From b7a80a1457d89742973e989ab5b67c74be166dd6 Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Tue, 21 May 2024 14:44:30 +0100 Subject: [PATCH 1/9] Intern the juju/charm package Interns the juju/charm package so that its dependencys are managed directly by the juju/juju package. Prior to this change, each major version of juju required changes to the charm package. Releasing a new version of the charm package is time consuming and hard to get right when an older major version has a bug with the assoicated charm package. Rev'ing the package in the correct way is time consuming. This should reduce the complexity of juju/juju where the core charm logic is outsourced to another package. Concretely the all code now points to the internal/charm package. We will have to spend time slowly merging bother core/charm and internal/charm. External clients will probably want access to this, but we should resist that and expose interfaces for reading charms and bundles. Complex reading of charms or bundles should be then interned to the api itself (potentially with vistors or adapters). Lastly, there is a package of charms that are currently living in internal/charm/internal/test-charm-repo, those should be moved to testcharms/charm-repo. --- api/agent/uniter/endpoint.go | 2 +- api/agent/uniter/relation.go | 2 +- api/agent/uniter/relation_test.go | 2 +- api/agent/uniter/relationunit_test.go | 2 +- api/agent/uniter/unit.go | 2 +- api/agent/uniter/unit_test.go | 2 +- api/client/application/client.go | 2 +- api/client/application/client_test.go | 2 +- api/client/applicationoffers/client.go | 2 +- api/client/applicationoffers/client_test.go | 2 +- api/client/charms/client.go | 4 +- api/client/charms/client_test.go | 4 +- api/client/charms/downloader_s3.go | 2 +- api/client/charms/localcharmclient.go | 2 +- api/client/charms/localcharmclient_test.go | 2 +- api/client/payloads/client_test.go | 2 +- api/client/payloads/helpers.go | 2 +- api/client/payloads/helpers_test.go | 2 +- api/client/resources/client.go | 2 +- api/client/resources/client_upload_test.go | 2 +- api/client/resources/helpers.go | 2 +- api/client/resources/helpers_test.go | 2 +- api/client/resources/upload.go | 2 +- api/common/charm/charmorigin.go | 2 +- api/common/charm/charmorigin_test.go | 2 +- api/common/charms/common.go | 4 +- api/common/charms/common_test.go | 4 +- .../caasapplicationprovisioner/client.go | 2 +- .../caasapplicationprovisioner/client_test.go | 2 +- api/controller/migrationmaster/client.go | 2 +- api/controller/migrationmaster/client_test.go | 2 +- apiserver/charms.go | 2 +- apiserver/charms_test.go | 2 +- apiserver/common/charms/appcharminfo_test.go | 4 +- apiserver/common/charms/charminfo_test.go | 2 +- apiserver/common/charms/common.go | 4 +- apiserver/common/charms/mocks/mocks.go | 2 +- apiserver/common/crossmodel/interface.go | 2 +- apiserver/common/firewall/firewall.go | 2 +- apiserver/common/firewall/firewall_test.go | 2 +- apiserver/common/unitstatus.go | 2 +- .../facades/agent/caasapplication/state.go | 2 +- .../instancemutater/instancemutater_test.go | 2 +- .../payloadshookcontext/unitfacade_test.go | 2 +- .../facades/agent/provisioner/interface.go | 2 +- .../agent/provisioner/mocks/package_mock.go | 2 +- .../agent/provisioner/provisioner_test.go | 2 +- apiserver/facades/agent/provisioner/shim.go | 2 +- .../facades/agent/uniter/goal-state_test.go | 2 +- .../facades/agent/uniter/networkinfo_test.go | 2 +- .../uniter/subordinaterelationwatcher.go | 2 +- apiserver/facades/agent/uniter/uniter.go | 2 +- .../agent/uniter/uniter_network_test.go | 2 +- apiserver/facades/agent/uniter/uniter_test.go | 2 +- .../facades/client/application/application.go | 2 +- .../client/application/application_test.go | 2 +- .../application/application_unit_test.go | 4 +- .../facades/client/application/backend.go | 4 +- .../facades/client/application/deploy.go | 4 +- .../facades/client/application/deploy_test.go | 2 +- .../client/application/deployrepository.go | 4 +- .../deployrepository_mocks_test.go | 2 +- .../application/deployrepository_test.go | 4 +- apiserver/facades/client/application/get.go | 2 +- .../facades/client/application/get_test.go | 2 +- .../application/mocks/application_mock.go | 4 +- .../application/mocks/lxdprofile_mock.go | 2 +- .../application/repository_mocks_test.go | 4 +- .../application/updatebase_mocks_test.go | 2 +- .../client/application/updatebase_test.go | 2 +- .../applicationoffers_test.go | 2 +- .../client/applicationoffers/base_test.go | 2 +- .../client/applicationoffers/mock_test.go | 2 +- apiserver/facades/client/bundle/bundle.go | 4 +- .../facades/client/bundle/bundle_test.go | 4 +- apiserver/facades/client/bundle/mock_test.go | 2 +- apiserver/facades/client/bundle/state.go | 2 +- apiserver/facades/client/charms/client.go | 2 +- .../client/charms/client_integration_test.go | 2 +- .../facades/client/charms/client_test.go | 2 +- .../client/charms/clientnormalize_test.go | 2 +- .../facades/client/charms/conversions.go | 2 +- apiserver/facades/client/charms/interface.go | 2 +- .../client/charms/interfaces/downloader.go | 2 +- .../facades/client/charms/mocks/repository.go | 4 +- .../facades/client/charms/mocks/state_mock.go | 2 +- apiserver/facades/client/client/api_test.go | 2 +- apiserver/facades/client/client/backend.go | 2 +- .../facades/client/client/client_test.go | 2 +- .../client/client/package_mock_test.go | 2 +- apiserver/facades/client/client/status.go | 2 +- .../facades/client/client/status_test.go | 2 +- .../facades/client/controller/backend.go | 2 +- .../client/controller/mocks/state_mock.go | 2 +- .../machinemanager/machinemanager_test.go | 2 +- .../machinemanager/package_mock_test.go | 2 +- .../facades/client/machinemanager/state.go | 2 +- .../client/machinemanager/upgradebase_test.go | 2 +- .../client/modelgeneration/interface.go | 2 +- .../modelgeneration/mocks/package_mock.go | 2 +- .../facades/client/modelgeneration/shim.go | 2 +- .../facades/client/payloads/facade_test.go | 2 +- .../facades/client/resources/base_test.go | 2 +- apiserver/facades/client/resources/facade.go | 4 +- .../facades/client/resources/mocks/backend.go | 2 +- .../facades/client/resources/repository.go | 2 +- .../resources/server_addpending_test.go | 4 +- .../resources/server_listresources_test.go | 2 +- .../facades/client/resources/server_test.go | 2 +- apiserver/facades/client/storage/mock_test.go | 2 +- .../caasapplicationprovisioner/mock_test.go | 2 +- .../caasapplicationprovisioner/provisioner.go | 2 +- .../provisioner_test.go | 4 +- .../caasapplicationprovisioner/state.go | 2 +- .../caasfirewaller/firewaller_test.go | 2 +- .../controller/caasfirewaller/mock_test.go | 2 +- .../charmdownloader/charmdownloader.go | 2 +- .../charmdownloader/charmdownloader_test.go | 2 +- .../controller/charmdownloader/interfaces.go | 2 +- .../controller/charmdownloader/mocks/mocks.go | 2 +- .../charmrevisionupdater/charmhub.go | 2 +- .../charmrevisionupdater/interface.go | 2 +- .../charmrevisionupdater/interface_test.go | 4 +- .../charmrevisionupdater/mocks/mocks.go | 2 +- .../charmrevisionupdater/updater.go | 4 +- .../charmrevisionupdater/updater_test.go | 4 +- .../crossmodelrelations.go | 2 +- .../crossmodelrelations_test.go | 2 +- .../remoterelations/remoterelations_test.go | 2 +- apiserver/mocks/resources_mock.go | 2 +- apiserver/objects.go | 2 +- apiserver/objects_test.go | 2 +- apiserver/resources.go | 2 +- apiserver/resources_mig.go | 2 +- apiserver/resources_mig_test.go | 2 +- apiserver/resources_test.go | 2 +- apiserver/testing/application.go | 2 +- caas/kubernetes/provider/bootstrap.go | 2 +- caas/kubernetes/provider/providerconfig.go | 2 +- cmd/juju/action/common.go | 2 +- cmd/juju/agree/agree/agree.go | 2 +- cmd/juju/application/bundle/bundle.go | 3 +- cmd/juju/application/bundle/bundle_test.go | 2 +- cmd/juju/application/bundle/interface.go | 2 +- .../bundle/mocks/bundledatasource_mock.go | 2 +- cmd/juju/application/bundle_test.go | 2 +- cmd/juju/application/deploy.go | 2 +- cmd/juju/application/deploy_test.go | 4 +- cmd/juju/application/deployer/bundle.go | 2 +- cmd/juju/application/deployer/bundle_test.go | 2 +- .../application/deployer/bundlehandler.go | 4 +- .../deployer/bundlehandler_test.go | 4 +- cmd/juju/application/deployer/charm.go | 2 +- cmd/juju/application/deployer/charm_test.go | 4 +- cmd/juju/application/deployer/deployer.go | 4 +- .../application/deployer/deployer_test.go | 4 +- cmd/juju/application/deployer/interface.go | 4 +- cmd/juju/application/deployer/lxdprofile.go | 2 +- .../application/deployer/mocks/charm_mock.go | 2 +- .../application/deployer/mocks/deploy_mock.go | 4 +- .../deployer/mocks/resolver_mock.go | 2 +- cmd/juju/application/deployer/resource.go | 2 +- cmd/juju/application/diffbundle.go | 2 +- cmd/juju/application/diffbundle_test.go | 2 +- cmd/juju/application/export_test.go | 2 +- cmd/juju/application/refresh.go | 2 +- cmd/juju/application/refresh_test.go | 4 +- .../application/refresher/charm_mock_test.go | 2 +- cmd/juju/application/refresher/interface.go | 2 +- cmd/juju/application/refresher/refresher.go | 2 +- .../refresher/refresher_mock_test.go | 2 +- .../application/refresher/refresher_test.go | 2 +- .../application/refresher/store_mock_test.go | 2 +- cmd/juju/application/store/charmadaptor.go | 2 +- .../application/store/charmadaptor_test.go | 2 +- cmd/juju/application/store/interface.go | 2 +- .../application/store/mocks/charm_mock.go | 2 +- .../application/store/mocks/store_mock.go | 2 +- cmd/juju/application/store/resolve.go | 2 +- cmd/juju/application/store/store.go | 2 +- cmd/juju/application/store/store_test.go | 2 +- cmd/juju/application/utils/interface.go | 2 +- .../utils/mocks/charmresource_mock.go | 2 +- cmd/juju/application/utils/origin.go | 2 +- cmd/juju/application/utils/utils.go | 2 +- cmd/juju/application/utils/utils_test.go | 4 +- cmd/juju/charmhub/convert.go | 2 +- cmd/juju/charmhub/data.go | 2 +- cmd/juju/charmhub/download.go | 2 +- cmd/juju/charmhub/info.go | 2 +- cmd/juju/charmhub/infowriter.go | 2 +- cmd/juju/charmhub/infowriter_test.go | 2 +- cmd/juju/commands/bootstrap.go | 2 +- cmd/juju/commands/helptool.go | 2 +- cmd/juju/crossmodel/find_test.go | 2 +- cmd/juju/crossmodel/list.go | 2 +- cmd/juju/crossmodel/list_test.go | 2 +- cmd/juju/crossmodel/remoteendpoints.go | 2 +- cmd/juju/crossmodel/show_test.go | 2 +- cmd/juju/payload/util_test.go | 2 +- cmd/juju/resource/charmresources.go | 4 +- cmd/juju/resource/charmresources_test.go | 4 +- cmd/juju/resource/deploy.go | 2 +- cmd/juju/resource/deploy_test.go | 2 +- cmd/juju/resource/formatter.go | 2 +- cmd/juju/resource/formatter_test.go | 2 +- cmd/juju/resource/list_test.go | 2 +- cmd/juju/resource/output_tabular_test.go | 2 +- cmd/juju/resource/resource.go | 2 +- cmd/juju/resource/stub_test.go | 2 +- cmd/juju/resource/upload.go | 2 +- cmd/juju/resource/upload_test.go | 2 +- cmd/juju/resource/util_test.go | 2 +- cmd/juju/resource/validation.go | 2 +- cmd/juju/ssh/debugcode_test.go | 2 +- cmd/juju/ssh/debughooks.go | 4 +- cmd/juju/ssh/debughooks_test.go | 2 +- cmd/juju/ssh/interface.go | 2 +- cmd/juju/ssh/mocks/package_mock.go | 2 +- cmd/juju/ssh/ssh_container_test.go | 2 +- cmd/juju/status/formatter.go | 2 +- cmd/juju/status/output_tabular.go | 4 +- cmd/juju/status/status_internal_test.go | 2 +- cmd/jujud-controller/agent/bootstrap_test.go | 2 +- core/actions/actions.go | 2 +- core/assumes/featureset.go | 2 +- core/assumes/sat_checker.go | 2 +- core/assumes/sat_checker_test.go | 2 +- core/base/base.go | 2 +- core/base/base_test.go | 2 +- core/charm/adaptor.go | 2 +- core/charm/channel.go | 2 +- core/charm/charm_mock_test.go | 2 +- core/charm/charmpath.go | 2 +- core/charm/charmpath_test.go | 2 +- core/charm/computedbase.go | 2 +- core/charm/computedbase_test.go | 2 +- core/charm/format.go | 2 +- core/charm/format_test.go | 2 +- core/charm/origin.go | 2 +- core/charm/repository.go | 4 +- core/crossmodel/interface.go | 2 +- core/crossmodel/params.go | 2 +- core/model/charm_mock_test.go | 2 +- core/payloads/filter_test.go | 2 +- core/payloads/payload.go | 2 +- core/payloads/payload_test.go | 2 +- core/resources/application.go | 2 +- core/resources/application_test.go | 2 +- core/resources/content.go | 2 +- core/resources/resource.go | 2 +- core/resources/resource_test.go | 2 +- core/resources/serialization.go | 2 +- core/resources/serialization_test.go | 2 +- core/resources/testing/resource.go | 2 +- core/resources/util_test.go | 2 +- core/settings/settings.go | 2 +- .../application/service/package_mock_test.go | 2 +- domain/application/service/params.go | 2 +- domain/application/service/service.go | 2 +- domain/application/service/service_test.go | 2 +- domain/storage/defaults.go | 2 +- domain/storage/defaults_test.go | 2 +- domain/storage/validation.go | 2 +- domain/storage/validation_test.go | 2 +- environs/bootstrap/bootstrap.go | 2 +- environs/bootstrap/bootstrap_test.go | 2 +- go.mod | 5 +- go.sum | 2 - internal/bootstrap/bootstrap_mock_test.go | 2 +- internal/bootstrap/charm_mock_test.go | 4 +- internal/bootstrap/deployer.go | 2 +- internal/bootstrap/deployer_test.go | 2 +- internal/bootstrap/downloader_mock_test.go | 2 +- internal/bootstrap/package_test.go | 2 +- internal/bundle/changes/changes.go | 2 +- internal/bundle/changes/changes_test.go | 2 +- internal/bundle/changes/diff.go | 2 +- internal/bundle/changes/diff_test.go | 2 +- internal/bundle/changes/handlers.go | 2 +- internal/bundle/changes/handlers_test.go | 2 +- internal/bundle/changes/model.go | 2 +- internal/bundle/changes/model_test.go | 2 +- internal/charm/actions.go | 308 +++ internal/charm/actions_test.go | 1097 +++++++++ internal/charm/assumes/expression.go | 61 + internal/charm/assumes/package_test.go | 14 + internal/charm/assumes/parser.go | 323 +++ internal/charm/assumes/parser_test.go | 406 ++++ internal/charm/base.go | 101 + internal/charm/base_test.go | 177 ++ internal/charm/bundle.go | 43 + internal/charm/bundle_test.go | 102 + internal/charm/bundlearchive.go | 116 + internal/charm/bundlearchive_test.go | 113 + internal/charm/bundledata.go | 1281 ++++++++++ internal/charm/bundledata_test.go | 1559 ++++++++++++ internal/charm/bundledatasrc.go | 270 +++ internal/charm/bundledatasrc_test.go | 256 ++ internal/charm/bundledir.go | 84 + internal/charm/bundledir_test.go | 58 + internal/charm/channel.go | 179 ++ internal/charm/channel_test.go | 194 ++ internal/charm/charm.go | 167 ++ internal/charm/charm_test.go | 297 +++ internal/charm/charmarchive.go | 325 +++ internal/charm/charmarchive_test.go | 455 ++++ internal/charm/charmbase.go | 65 + internal/charm/charmdir.go | 563 +++++ internal/charm/charmdir_test.go | 731 ++++++ internal/charm/config.go | 265 +++ internal/charm/config_test.go | 500 ++++ internal/charm/downloader/downloader.go | 2 +- internal/charm/downloader/downloader_test.go | 2 +- .../downloader/mocks/charm_archive_mocks.go | 2 +- .../charm/downloader/mocks/charm_mocks.go | 2 +- internal/charm/export_test.go | 22 + internal/charm/extra_bindings.go | 76 + internal/charm/extra_bindings_test.go | 69 + internal/charm/hooks/hooks.go | 173 ++ internal/charm/hooks/hooks_test.go | 49 + internal/charm/hooks/package_test.go | 14 + .../test-charm-repo/bundle/bad/README.md | 1 + .../test-charm-repo/bundle/bad/bundle.yaml | 11 + .../bundle/openstack/README.md | 46 + .../bundle/openstack/bundle.yaml | 202 ++ .../bundle/wordpress-multidoc/README.md | 1 + .../bundle/wordpress-multidoc/bundle.yaml | 23 + .../wordpress-simple-multidoc/README.md | 1 + .../wordpress-simple-multidoc/bundle.yaml | 13 + .../bundle/wordpress-simple/README.md | 1 + .../bundle/wordpress-simple/bundle.yaml | 8 + .../bundle/wordpress-with-logging/README.md | 1 + .../bundle/wordpress-with-logging/bundle.yaml | 20 + .../all-hooks/hooks/bar-relation-broken | 2 + .../all-hooks/hooks/bar-relation-changed | 2 + .../all-hooks/hooks/bar-relation-departed | 2 + .../all-hooks/hooks/bar-relation-joined | 2 + .../quantal/all-hooks/hooks/config-changed | 2 + .../all-hooks/hooks/foo-relation-broken | 2 + .../all-hooks/hooks/foo-relation-changed | 2 + .../all-hooks/hooks/foo-relation-departed | 2 + .../all-hooks/hooks/foo-relation-joined | 2 + .../quantal/all-hooks/hooks/install | 2 + .../quantal/all-hooks/hooks/otherdata | 1 + .../all-hooks/hooks/self-relation-broken | 2 + .../all-hooks/hooks/self-relation-changed | 2 + .../all-hooks/hooks/self-relation-departed | 2 + .../all-hooks/hooks/self-relation-joined | 2 + .../quantal/all-hooks/hooks/start | 2 + .../quantal/all-hooks/hooks/stop | 2 + .../quantal/all-hooks/hooks/subdir/stuff | 1 + .../quantal/all-hooks/hooks/upgrade-charm | 2 + .../quantal/all-hooks/manifest.yaml | 5 + .../quantal/all-hooks/metadata.yaml | 12 + .../quantal/all-hooks/revision | 1 + .../quantal/bad-bases/actions.yaml | 7 + .../quantal/bad-bases/config.yaml | 5 + .../quantal/bad-bases/hooks/install | 2 + .../quantal/bad-bases/metadata.yaml | 20 + .../quantal/bad-bases/revision | 1 + .../test-charm-repo/quantal/bad/actions.yaml | 7 + .../test-charm-repo/quantal/bad/config.yaml | 5 + .../test-charm-repo/quantal/bad/hooks/install | 2 + .../test-charm-repo/quantal/bad/revision | 1 + .../quantal/category/.dir/ignored | 0 .../test-charm-repo/quantal/category/.ignored | 1 + .../quantal/category/metadata.yaml | 6 + .../quantal/dummy-actions/.notignored | 1 + .../quantal/dummy-actions/actions.yaml | 7 + .../quantal/dummy-actions/actions/snapshot | 2 + .../quantal/dummy-actions/build/ignored | 0 .../quantal/dummy-actions/config.yaml | 5 + .../quantal/dummy-actions/empty/.gitkeep | 0 .../quantal/dummy-actions/hooks/install | 2 + .../quantal/dummy-actions/lxd-profile.yaml | 9 + .../quantal/dummy-actions/manifest.yaml | 5 + .../quantal/dummy-actions/metadata.yaml | 5 + .../quantal/dummy-actions/revision | 1 + .../quantal/dummy-actions/src/hello.c | 7 + .../test-charm-repo/quantal/dummy/.notignored | 1 + .../quantal/dummy/actions.yaml | 7 + .../quantal/dummy/build/ignored | 0 .../test-charm-repo/quantal/dummy/config.yaml | 5 + .../quantal/dummy/empty/.gitkeep | 0 .../quantal/dummy/hooks/install | 2 + .../quantal/dummy/lxd-profile.yaml | 9 + .../quantal/dummy/manifest.yaml | 5 + .../quantal/dummy/metadata.yaml | 5 + .../test-charm-repo/quantal/dummy/revision | 1 + .../test-charm-repo/quantal/dummy/src/hello.c | 7 + .../quantal/format-containers/metadata.yaml | 8 + .../format-containersmanifest/manifest.yaml | 5 + .../format-containersmanifest/metadata.yaml | 8 + .../quantal/format-series/metadata.yaml | 6 + .../format-seriesmanifest/manifest.yaml | 5 + .../format-seriesmanifest/metadata.yaml | 6 + .../quantal/format/metadata.yaml | 4 + .../quantal/format2/.dir/ignored | 0 .../test-charm-repo/quantal/format2/.ignored | 1 + .../quantal/format2/metadata.yaml | 6 + .../quantal/juju-charm/.notignored | 1 + .../quantal/juju-charm/actions.yaml | 7 + .../quantal/juju-charm/actions/juju-snapshot | 2 + .../quantal/juju-charm/build/ignored | 0 .../quantal/juju-charm/config.yaml | 5 + .../quantal/juju-charm/empty/.gitkeep | 0 .../quantal/juju-charm/hooks/install | 2 + .../quantal/juju-charm/lxd-profile.yaml | 9 + .../quantal/juju-charm/metadata.yaml | 8 + .../quantal/juju-charm/revision | 1 + .../quantal/logging/hooks/.gitkeep | 0 .../quantal/logging/metadata.yaml | 16 + .../test-charm-repo/quantal/logging/revision | 1 + .../quantal/monitoring/hooks/.gitkeep | 0 .../quantal/monitoring/metadata.yaml | 16 + .../quantal/mysql-alternative/metadata.yaml | 9 + .../quantal/mysql-alternative/revision | 1 + .../quantal/mysql/metadata.yaml | 5 + .../test-charm-repo/quantal/mysql/revision | 1 + .../quantal/riak/metadata.yaml | 11 + .../test-charm-repo/quantal/riak/revision | 1 + .../quantal/terms/metadata.yaml | 5 + .../quantal/terracotta/metadata.yaml | 15 + .../quantal/terracotta/revision | 1 + .../quantal/upgrade1/metadata.yaml | 5 + .../test-charm-repo/quantal/upgrade1/revision | 1 + .../quantal/upgrade2/metadata.yaml | 5 + .../test-charm-repo/quantal/upgrade2/revision | 1 + .../quantal/varnish-alternative/hooks/install | 3 + .../quantal/varnish-alternative/metadata.yaml | 5 + .../quantal/varnish-alternative/revision | 1 + .../quantal/varnish/manifest.yaml | 5 + .../quantal/varnish/metadata.yaml | 5 + .../test-charm-repo/quantal/varnish/revision | 1 + .../quantal/versioned/metadata.yaml | 5 + .../test-charm-repo/quantal/versioned/version | 1 + .../quantal/wordpress/actions/.gitkeep | 0 .../quantal/wordpress/config.yaml | 3 + .../quantal/wordpress/hooks/.gitkeep | 0 .../quantal/wordpress/manifest.yaml | 5 + .../quantal/wordpress/metadata.yaml | 27 + .../quantal/wordpress/revision | 1 + .../series/format2/build/ignored | 0 .../series/format2/hooks/symlink | 1 + internal/charm/jujuignore.go | 283 +++ internal/charm/jujuignore_test.go | 227 ++ internal/charm/lxdprofile.go | 80 + internal/charm/lxdprofile_test.go | 164 ++ internal/charm/manifest.go | 127 + internal/charm/manifest_test.go | 64 + internal/charm/meta.go | 1513 ++++++++++++ internal/charm/meta_test.go | 2091 +++++++++++++++++ internal/charm/offerurl.go | 171 ++ internal/charm/offerurl_test.go | 180 ++ internal/charm/overlay.go | 772 ++++++ internal/charm/overlay_internal_test.go | 64 + internal/charm/overlay_test.go | 1013 ++++++++ internal/charm/package_test.go | 14 + internal/charm/payloads.go | 73 + internal/charm/payloads_test.go | 96 + internal/charm/repository/charmhub.go | 4 +- internal/charm/repository/charmhub_test.go | 4 +- .../repository/mocks/charmhub_client_mock.go | 2 +- internal/charm/resource/fingerprint.go | 66 + internal/charm/resource/fingerprint_test.go | 143 ++ internal/charm/resource/meta.go | 65 + internal/charm/resource/meta_test.go | 118 + internal/charm/resource/origin.go | 50 + internal/charm/resource/origin_test.go | 58 + internal/charm/resource/package_test.go | 14 + internal/charm/resource/resource.go | 94 + internal/charm/resource/resource_test.go | 219 ++ internal/charm/resource/sort.go | 19 + internal/charm/resource/type.go | 50 + internal/charm/resource/type_test.go | 83 + internal/charm/resources.go | 86 + internal/charm/resources_test.go | 77 + internal/charm/url.go | 477 ++++ internal/charm/url_test.go | 304 +++ internal/charm/version.go | 26 + internal/charm/version_test.go | 32 + internal/charmhub/client.go | 2 +- internal/charmhub/download.go | 2 +- .../cloudconfig/instancecfg/instancecfg.go | 2 +- internal/cloudconfig/podcfg/image.go | 2 +- internal/cloudconfig/podcfg/image_test.go | 2 +- internal/cloudconfig/userdatacfg_test.go | 2 +- internal/cloudconfig/userdatacfg_unix.go | 2 +- internal/container/broker/lxd-broker_test.go | 2 +- internal/migration/migration.go | 2 +- internal/migration/migration_test.go | 2 +- internal/resource/charmhub.go | 2 +- internal/resource/charmhub_test.go | 4 +- internal/resource/export_test.go | 2 +- internal/resource/interfaces.go | 2 +- internal/resource/mocks/cache_mock.go | 2 +- internal/resource/opener.go | 4 +- internal/resource/opener_test.go | 4 +- internal/resource/resource.go | 2 +- internal/resource/retryclient.go | 2 +- .../worker/bootstrap/bootstrap_mock_test.go | 2 +- internal/worker/bootstrap/deployer.go | 2 +- internal/worker/bootstrap/worker_test.go | 2 +- .../worker/caasapplicationprovisioner/ops.go | 2 +- .../caasapplicationprovisioner/ops_test.go | 2 +- internal/worker/caasfirewaller/worker.go | 2 +- internal/worker/caasfirewaller/worker_test.go | 2 +- internal/worker/firewaller/firewaller.go | 2 +- internal/worker/uniter/api/domain_mocks.go | 2 +- internal/worker/uniter/api/interface.go | 2 +- internal/worker/uniter/charm/bundles.go | 2 +- internal/worker/uniter/charm/bundles_test.go | 2 +- internal/worker/uniter/charm/charm_test.go | 2 +- .../worker/uniter/charm/manifest_deployer.go | 2 +- internal/worker/uniter/container/workload.go | 2 +- internal/worker/uniter/entity_mocks_test.go | 2 +- internal/worker/uniter/hook/hook.go | 2 +- internal/worker/uniter/hook/hook_test.go | 2 +- internal/worker/uniter/leadership/resolver.go | 2 +- .../worker/uniter/leadership/resolver_test.go | 2 +- internal/worker/uniter/mockrunner_test.go | 2 +- internal/worker/uniter/op_callbacks.go | 2 +- internal/worker/uniter/operation/deploy.go | 2 +- .../worker/uniter/operation/deploy_test.go | 2 +- .../worker/uniter/operation/executor_test.go | 2 +- .../worker/uniter/operation/factory_test.go | 2 +- .../uniter/operation/failaction_test.go | 2 +- internal/worker/uniter/operation/leader.go | 2 +- .../worker/uniter/operation/leader_test.go | 2 +- .../uniter/operation/remoteinit_test.go | 2 +- .../worker/uniter/operation/runaction_test.go | 2 +- internal/worker/uniter/operation/runhook.go | 2 +- .../worker/uniter/operation/runhook_test.go | 2 +- .../worker/uniter/operation/state_test.go | 2 +- internal/worker/uniter/operation/util_test.go | 2 +- internal/worker/uniter/reboot/resolver.go | 2 +- internal/worker/uniter/relation/mock_test.go | 2 +- internal/worker/uniter/relation/relationer.go | 2 +- .../worker/uniter/relation/relationer_test.go | 4 +- internal/worker/uniter/relation/resolver.go | 2 +- .../worker/uniter/relation/resolver_test.go | 4 +- internal/worker/uniter/relation/state.go | 2 +- internal/worker/uniter/relation/state_test.go | 2 +- .../worker/uniter/relation/statetracker.go | 4 +- .../uniter/relation/statetracker_test.go | 4 +- internal/worker/uniter/resolver.go | 4 +- .../worker/uniter/resolver/locker_test.go | 2 +- internal/worker/uniter/resolver/loop.go | 4 +- internal/worker/uniter/resolver/loop_test.go | 2 +- internal/worker/uniter/resolver/opfactory.go | 2 +- .../worker/uniter/resolver/opfactory_test.go | 2 +- internal/worker/uniter/resolver_test.go | 2 +- .../worker/uniter/runner/context/context.go | 4 +- .../uniter/runner/context/context_test.go | 2 +- .../uniter/runner/context/contextfactory.go | 2 +- .../runner/context/contextfactory_test.go | 2 +- .../runner/context/payloads/context_test.go | 2 +- .../uniter/runner/context/relation_test.go | 2 +- .../runner/context/resources/base_test.go | 2 +- .../runner/context/resources/content.go | 2 +- .../runner/context/resources/content_test.go | 2 +- .../runner/context/resources/context.go | 2 +- .../runner/context/resources/resource.go | 2 +- .../worker/uniter/runner/context/util_test.go | 2 +- internal/worker/uniter/runner/factory.go | 2 +- internal/worker/uniter/runner/factory_test.go | 2 +- .../worker/uniter/runner/jujuc/action-set.go | 2 +- .../worker/uniter/runner/jujuc/context.go | 2 +- .../uniter/runner/jujuc/jujuctesting/suite.go | 2 +- .../uniter/runner/jujuc/jujuctesting/unit.go | 2 +- .../uniter/runner/jujuc/mocks/context_mock.go | 2 +- .../uniter/runner/jujuc/payload-register.go | 2 +- .../runner/jujuc/payload-register_test.go | 2 +- .../worker/uniter/runner/jujuc/restricted.go | 2 +- .../uniter/runner/mocks/context_mock.go | 2 +- internal/worker/uniter/runner/runner_test.go | 2 +- internal/worker/uniter/runner/util_test.go | 2 +- internal/worker/uniter/secrets/resolver.go | 2 +- .../worker/uniter/secrets/resolver_test.go | 2 +- .../worker/uniter/secrets/secrets_test.go | 2 +- internal/worker/uniter/secrets/state.go | 2 +- internal/worker/uniter/storage/attachments.go | 2 +- .../worker/uniter/storage/attachments_test.go | 2 +- internal/worker/uniter/storage/resolver.go | 2 +- internal/worker/uniter/storage/state.go | 2 +- internal/worker/uniter/storage/state_test.go | 2 +- internal/worker/uniter/uniter.go | 2 +- internal/worker/uniter/uniter_test.go | 2 +- .../worker/uniter/upgradeseries/resolver.go | 2 +- .../uniter/upgradeseries/resolver_test.go | 2 +- internal/worker/uniter/util_test.go | 2 +- .../verifycharmprofile/verifycharmprofile.go | 2 +- juju/testing/utils.go | 2 +- rpc/params/charms.go | 4 +- rpc/params/crossmodel.go | 2 +- rpc/params/multiwatcher.go | 2 +- rpc/params/params_test.go | 2 +- state/allwatcher.go | 2 +- state/allwatcher_internal_test.go | 2 +- state/application.go | 2 +- state/application_test.go | 2 +- state/applicationoffers.go | 2 +- state/applicationoffers_test.go | 2 +- state/charm.go | 2 +- state/charm_test.go | 2 +- state/cleanup_test.go | 2 +- state/devices.go | 2 +- state/endpoint.go | 2 +- state/endpoint_bindings.go | 2 +- state/endpoint_test.go | 2 +- state/export_test.go | 2 +- state/filesystem.go | 2 +- state/filesystem_test.go | 2 +- state/machine.go | 2 +- state/migration_export.go | 2 +- state/migration_export_test.go | 4 +- state/migration_import.go | 2 +- state/migration_import_test.go | 2 +- state/migration_internal_test.go | 2 +- state/migrations/remoteapplications.go | 2 +- state/mocks/resources_mock.go | 2 +- state/model_test.go | 2 +- state/modelgeneration.go | 2 +- state/modelgeneration_test.go | 2 +- state/payloads_ns.go | 2 +- state/payloads_test.go | 2 +- state/relation.go | 2 +- state/relation_internal_test.go | 2 +- state/relation_test.go | 2 +- state/relationunit.go | 2 +- state/relationunit_test.go | 2 +- state/remoteapplication.go | 2 +- state/remoteapplication_test.go | 2 +- state/resources.go | 2 +- state/resources_staged_test.go | 2 +- state/resources_test.go | 4 +- state/secrets_test.go | 2 +- state/state.go | 2 +- state/state_test.go | 2 +- state/storage.go | 2 +- state/storage_test.go | 2 +- state/unit.go | 2 +- state/unit_test.go | 2 +- state/watcher.go | 2 +- testcharms/charm.go | 2 +- testcharms/repo/repo.go | 2 +- testing/factory/factory.go | 4 +- 648 files changed, 20702 insertions(+), 514 deletions(-) create mode 100644 internal/charm/actions.go create mode 100644 internal/charm/actions_test.go create mode 100644 internal/charm/assumes/expression.go create mode 100644 internal/charm/assumes/package_test.go create mode 100644 internal/charm/assumes/parser.go create mode 100644 internal/charm/assumes/parser_test.go create mode 100644 internal/charm/base.go create mode 100644 internal/charm/base_test.go create mode 100644 internal/charm/bundle.go create mode 100644 internal/charm/bundle_test.go create mode 100644 internal/charm/bundlearchive.go create mode 100644 internal/charm/bundlearchive_test.go create mode 100644 internal/charm/bundledata.go create mode 100644 internal/charm/bundledata_test.go create mode 100644 internal/charm/bundledatasrc.go create mode 100644 internal/charm/bundledatasrc_test.go create mode 100644 internal/charm/bundledir.go create mode 100644 internal/charm/bundledir_test.go create mode 100644 internal/charm/channel.go create mode 100644 internal/charm/channel_test.go create mode 100644 internal/charm/charm.go create mode 100644 internal/charm/charm_test.go create mode 100644 internal/charm/charmarchive.go create mode 100644 internal/charm/charmarchive_test.go create mode 100644 internal/charm/charmbase.go create mode 100644 internal/charm/charmdir.go create mode 100644 internal/charm/charmdir_test.go create mode 100644 internal/charm/config.go create mode 100644 internal/charm/config_test.go create mode 100644 internal/charm/export_test.go create mode 100644 internal/charm/extra_bindings.go create mode 100644 internal/charm/extra_bindings_test.go create mode 100644 internal/charm/hooks/hooks.go create mode 100644 internal/charm/hooks/hooks_test.go create mode 100644 internal/charm/hooks/package_test.go create mode 100644 internal/charm/internal/test-charm-repo/bundle/bad/README.md create mode 100644 internal/charm/internal/test-charm-repo/bundle/bad/bundle.yaml create mode 100644 internal/charm/internal/test-charm-repo/bundle/openstack/README.md create mode 100644 internal/charm/internal/test-charm-repo/bundle/openstack/bundle.yaml create mode 100644 internal/charm/internal/test-charm-repo/bundle/wordpress-multidoc/README.md create mode 100644 internal/charm/internal/test-charm-repo/bundle/wordpress-multidoc/bundle.yaml create mode 100644 internal/charm/internal/test-charm-repo/bundle/wordpress-simple-multidoc/README.md create mode 100644 internal/charm/internal/test-charm-repo/bundle/wordpress-simple-multidoc/bundle.yaml create mode 100644 internal/charm/internal/test-charm-repo/bundle/wordpress-simple/README.md create mode 100644 internal/charm/internal/test-charm-repo/bundle/wordpress-simple/bundle.yaml create mode 100644 internal/charm/internal/test-charm-repo/bundle/wordpress-with-logging/README.md create mode 100644 internal/charm/internal/test-charm-repo/bundle/wordpress-with-logging/bundle.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-broken create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-changed create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-departed create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-joined create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/config-changed create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-broken create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-changed create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-departed create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-joined create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/install create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/otherdata create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-broken create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-changed create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-departed create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-joined create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/start create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/stop create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/subdir/stuff create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/upgrade-charm create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/manifest.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/all-hooks/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/bad-bases/actions.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/bad-bases/config.yaml create mode 100755 internal/charm/internal/test-charm-repo/quantal/bad-bases/hooks/install create mode 100644 internal/charm/internal/test-charm-repo/quantal/bad-bases/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/bad-bases/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/bad/actions.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/bad/config.yaml create mode 100755 internal/charm/internal/test-charm-repo/quantal/bad/hooks/install create mode 100644 internal/charm/internal/test-charm-repo/quantal/bad/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/category/.dir/ignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/category/.ignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/category/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/.notignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/actions.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/actions/snapshot create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/build/ignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/config.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/empty/.gitkeep create mode 100755 internal/charm/internal/test-charm-repo/quantal/dummy-actions/hooks/install create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/lxd-profile.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/manifest.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy-actions/src/hello.c create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/.notignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/actions.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/build/ignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/config.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/empty/.gitkeep create mode 100755 internal/charm/internal/test-charm-repo/quantal/dummy/hooks/install create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/lxd-profile.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/manifest.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/dummy/src/hello.c create mode 100644 internal/charm/internal/test-charm-repo/quantal/format-containers/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/format-containersmanifest/manifest.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/format-containersmanifest/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/format-series/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/format-seriesmanifest/manifest.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/format-seriesmanifest/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/format/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/format2/.dir/ignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/format2/.ignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/format2/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/.notignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/actions.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/actions/juju-snapshot create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/build/ignored create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/config.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/empty/.gitkeep create mode 100755 internal/charm/internal/test-charm-repo/quantal/juju-charm/hooks/install create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/lxd-profile.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/juju-charm/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/logging/hooks/.gitkeep create mode 100644 internal/charm/internal/test-charm-repo/quantal/logging/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/logging/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/monitoring/hooks/.gitkeep create mode 100644 internal/charm/internal/test-charm-repo/quantal/monitoring/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/mysql-alternative/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/mysql-alternative/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/mysql/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/mysql/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/riak/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/riak/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/terms/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/terracotta/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/terracotta/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/upgrade1/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/upgrade1/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/upgrade2/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/upgrade2/revision create mode 100755 internal/charm/internal/test-charm-repo/quantal/varnish-alternative/hooks/install create mode 100644 internal/charm/internal/test-charm-repo/quantal/varnish-alternative/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/varnish-alternative/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/varnish/manifest.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/varnish/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/varnish/revision create mode 100644 internal/charm/internal/test-charm-repo/quantal/versioned/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/versioned/version create mode 100644 internal/charm/internal/test-charm-repo/quantal/wordpress/actions/.gitkeep create mode 100644 internal/charm/internal/test-charm-repo/quantal/wordpress/config.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/wordpress/hooks/.gitkeep create mode 100644 internal/charm/internal/test-charm-repo/quantal/wordpress/manifest.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/wordpress/metadata.yaml create mode 100644 internal/charm/internal/test-charm-repo/quantal/wordpress/revision create mode 100644 internal/charm/internal/test-charm-repo/series/format2/build/ignored create mode 120000 internal/charm/internal/test-charm-repo/series/format2/hooks/symlink create mode 100644 internal/charm/jujuignore.go create mode 100644 internal/charm/jujuignore_test.go create mode 100644 internal/charm/lxdprofile.go create mode 100644 internal/charm/lxdprofile_test.go create mode 100644 internal/charm/manifest.go create mode 100644 internal/charm/manifest_test.go create mode 100644 internal/charm/meta.go create mode 100644 internal/charm/meta_test.go create mode 100644 internal/charm/offerurl.go create mode 100644 internal/charm/offerurl_test.go create mode 100644 internal/charm/overlay.go create mode 100644 internal/charm/overlay_internal_test.go create mode 100644 internal/charm/overlay_test.go create mode 100644 internal/charm/package_test.go create mode 100644 internal/charm/payloads.go create mode 100644 internal/charm/payloads_test.go create mode 100644 internal/charm/resource/fingerprint.go create mode 100644 internal/charm/resource/fingerprint_test.go create mode 100644 internal/charm/resource/meta.go create mode 100644 internal/charm/resource/meta_test.go create mode 100644 internal/charm/resource/origin.go create mode 100644 internal/charm/resource/origin_test.go create mode 100644 internal/charm/resource/package_test.go create mode 100644 internal/charm/resource/resource.go create mode 100644 internal/charm/resource/resource_test.go create mode 100644 internal/charm/resource/sort.go create mode 100644 internal/charm/resource/type.go create mode 100644 internal/charm/resource/type_test.go create mode 100644 internal/charm/resources.go create mode 100644 internal/charm/resources_test.go create mode 100644 internal/charm/url.go create mode 100644 internal/charm/url_test.go create mode 100644 internal/charm/version.go create mode 100644 internal/charm/version_test.go diff --git a/api/agent/uniter/endpoint.go b/api/agent/uniter/endpoint.go index 238ce50897c..43e4abf8334 100644 --- a/api/agent/uniter/endpoint.go +++ b/api/agent/uniter/endpoint.go @@ -4,7 +4,7 @@ package uniter import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" ) // Endpoint represents one endpoint of a relation. It is just a wrapper diff --git a/api/agent/uniter/relation.go b/api/agent/uniter/relation.go index ded825f9f0a..ead3e7efbd7 100644 --- a/api/agent/uniter/relation.go +++ b/api/agent/uniter/relation.go @@ -6,8 +6,8 @@ package uniter import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/life" diff --git a/api/agent/uniter/relation_test.go b/api/agent/uniter/relation_test.go index 1039b9ff7cd..7cfcfcbfe49 100644 --- a/api/agent/uniter/relation_test.go +++ b/api/agent/uniter/relation_test.go @@ -6,7 +6,7 @@ package uniter_test import ( "context" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/api/agent/uniter/relationunit_test.go b/api/agent/uniter/relationunit_test.go index dbcf2d0f278..e5937970080 100644 --- a/api/agent/uniter/relationunit_test.go +++ b/api/agent/uniter/relationunit_test.go @@ -6,7 +6,7 @@ package uniter_test import ( "context" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/api/agent/uniter/unit.go b/api/agent/uniter/unit.go index 87f704e53d8..6a46f26a4f3 100644 --- a/api/agent/uniter/unit.go +++ b/api/agent/uniter/unit.go @@ -7,8 +7,8 @@ import ( "context" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/common" diff --git a/api/agent/uniter/unit_test.go b/api/agent/uniter/unit_test.go index 8c11a732a4b..0ddce84c7a6 100644 --- a/api/agent/uniter/unit_test.go +++ b/api/agent/uniter/unit_test.go @@ -7,7 +7,7 @@ import ( "context" "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/api/client/application/client.go b/api/client/application/client.go index a0a9e5f8e02..783f522ca32 100644 --- a/api/client/application/client.go +++ b/api/client/application/client.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/base" diff --git a/api/client/application/client_test.go b/api/client/application/client_test.go index d8c90de2477..8004cc08774 100644 --- a/api/client/application/client_test.go +++ b/api/client/application/client_test.go @@ -7,8 +7,8 @@ import ( stderrors "errors" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/api/client/applicationoffers/client.go b/api/client/applicationoffers/client.go index 8a5826dc565..8b93a626b40 100644 --- a/api/client/applicationoffers/client.go +++ b/api/client/applicationoffers/client.go @@ -7,8 +7,8 @@ import ( "context" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/base" diff --git a/api/client/applicationoffers/client_test.go b/api/client/applicationoffers/client_test.go index ee6524cf04f..6ea7d4fd3e9 100644 --- a/api/client/applicationoffers/client_test.go +++ b/api/client/applicationoffers/client_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/api/client/charms/client.go b/api/client/charms/client.go index 4447f3811f4..509da21f16f 100644 --- a/api/client/charms/client.go +++ b/api/client/charms/client.go @@ -6,10 +6,10 @@ package charms import ( "context" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/collections/transform" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/api/base" api "github.com/juju/juju/api/client/resources" diff --git a/api/client/charms/client_test.go b/api/client/charms/client_test.go index 3d90034a920..d8ce5016ac0 100644 --- a/api/client/charms/client_test.go +++ b/api/client/charms/client_test.go @@ -6,9 +6,9 @@ package charms_test import ( "os" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/api/client/charms/downloader_s3.go b/api/client/charms/downloader_s3.go index 2497f1e3565..4bed91bf6f9 100644 --- a/api/client/charms/downloader_s3.go +++ b/api/client/charms/downloader_s3.go @@ -8,8 +8,8 @@ import ( "fmt" "io" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/api/base" "github.com/juju/juju/internal/downloader" diff --git a/api/client/charms/localcharmclient.go b/api/client/charms/localcharmclient.go index 65505418865..334a43c6d92 100644 --- a/api/client/charms/localcharmclient.go +++ b/api/client/charms/localcharmclient.go @@ -13,8 +13,8 @@ import ( "os" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/version/v2" "github.com/juju/juju/api/base" diff --git a/api/client/charms/localcharmclient_test.go b/api/client/charms/localcharmclient_test.go index 13d15202a3d..a9028406f21 100644 --- a/api/client/charms/localcharmclient_test.go +++ b/api/client/charms/localcharmclient_test.go @@ -8,8 +8,8 @@ import ( "net/http" "regexp" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" "go.uber.org/mock/gomock" diff --git a/api/client/payloads/client_test.go b/api/client/payloads/client_test.go index 5adfa4ec431..d26f8710dcd 100644 --- a/api/client/payloads/client_test.go +++ b/api/client/payloads/client_test.go @@ -4,7 +4,7 @@ package payloads_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/api/client/payloads/helpers.go b/api/client/payloads/helpers.go index 8b57c671012..f40f5445440 100644 --- a/api/client/payloads/helpers.go +++ b/api/client/payloads/helpers.go @@ -4,8 +4,8 @@ package payloads import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/payloads" diff --git a/api/client/payloads/helpers_test.go b/api/client/payloads/helpers_test.go index 3ce8ef7d85d..0b33e164962 100644 --- a/api/client/payloads/helpers_test.go +++ b/api/client/payloads/helpers_test.go @@ -4,7 +4,7 @@ package payloads import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/api/client/resources/client.go b/api/client/resources/client.go index b2e8f49137c..89737e32e6a 100644 --- a/api/client/resources/client.go +++ b/api/client/resources/client.go @@ -8,8 +8,8 @@ import ( "io" "strings" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/api/base" diff --git a/api/client/resources/client_upload_test.go b/api/client/resources/client_upload_test.go index d03c8306023..eca55e04847 100644 --- a/api/client/resources/client_upload_test.go +++ b/api/client/resources/client_upload_test.go @@ -12,8 +12,8 @@ import ( "strings" "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" "go.uber.org/mock/gomock" diff --git a/api/client/resources/helpers.go b/api/client/resources/helpers.go index b62d231d8e4..73f1b9dc6b1 100644 --- a/api/client/resources/helpers.go +++ b/api/client/resources/helpers.go @@ -4,8 +4,8 @@ package resources import ( - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" apiservererrors "github.com/juju/juju/apiserver/errors" diff --git a/api/client/resources/helpers_test.go b/api/client/resources/helpers_test.go index fef17a83eda..092495677c4 100644 --- a/api/client/resources/helpers_test.go +++ b/api/client/resources/helpers_test.go @@ -7,8 +7,8 @@ import ( "strings" "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/api/client/resources/upload.go b/api/client/resources/upload.go index 5fc580eae8f..24364325a91 100644 --- a/api/client/resources/upload.go +++ b/api/client/resources/upload.go @@ -9,8 +9,8 @@ import ( "mime" "net/http" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/core/resources" diff --git a/api/common/charm/charmorigin.go b/api/common/charm/charmorigin.go index 74462002b4e..71e7a9a28ee 100644 --- a/api/common/charm/charmorigin.go +++ b/api/common/charm/charmorigin.go @@ -4,8 +4,8 @@ package charm import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" diff --git a/api/common/charm/charmorigin_test.go b/api/common/charm/charmorigin_test.go index 14758f01f7b..43b224a59a4 100644 --- a/api/common/charm/charmorigin_test.go +++ b/api/common/charm/charmorigin_test.go @@ -4,7 +4,7 @@ package charm_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" gc "gopkg.in/check.v1" commoncharm "github.com/juju/juju/api/common/charm" diff --git a/api/common/charms/common.go b/api/common/charms/common.go index bb83357bf82..921e84325d1 100644 --- a/api/common/charms/common.go +++ b/api/common/charms/common.go @@ -7,9 +7,9 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/version/v2" diff --git a/api/common/charms/common_test.go b/api/common/charms/common_test.go index 8da4133e536..b8f122e03e8 100644 --- a/api/common/charms/common_test.go +++ b/api/common/charms/common_test.go @@ -4,8 +4,8 @@ package charms_test import ( - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/version/v2" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/api/controller/caasapplicationprovisioner/client.go b/api/controller/caasapplicationprovisioner/client.go index e2261ce3aa3..8ae73990d14 100644 --- a/api/controller/caasapplicationprovisioner/client.go +++ b/api/controller/caasapplicationprovisioner/client.go @@ -7,8 +7,8 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/version/v2" diff --git a/api/controller/caasapplicationprovisioner/client_test.go b/api/controller/caasapplicationprovisioner/client_test.go index 6612c02f9b9..4ff3c3e0c1e 100644 --- a/api/controller/caasapplicationprovisioner/client_test.go +++ b/api/controller/caasapplicationprovisioner/client_test.go @@ -4,8 +4,8 @@ package caasapplicationprovisioner_test import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/api/controller/migrationmaster/client.go b/api/controller/migrationmaster/client.go index e6c6e38a377..ab762fdc9f4 100644 --- a/api/controller/migrationmaster/client.go +++ b/api/controller/migrationmaster/client.go @@ -11,8 +11,8 @@ import ( "net/http" "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/version/v2" "gopkg.in/httprequest.v1" diff --git a/api/controller/migrationmaster/client_test.go b/api/controller/migrationmaster/client_test.go index 7846f269718..2fec981c4b5 100644 --- a/api/controller/migrationmaster/client_test.go +++ b/api/controller/migrationmaster/client_test.go @@ -12,8 +12,8 @@ import ( "strings" "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/charms.go b/apiserver/charms.go index b603fcc14d3..14f80a022d3 100644 --- a/apiserver/charms.go +++ b/apiserver/charms.go @@ -20,8 +20,8 @@ import ( "strconv" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ziputil "github.com/juju/utils/v4/zip" "github.com/juju/juju/apiserver/common" diff --git a/apiserver/charms_test.go b/apiserver/charms_test.go index 9e78fca3926..6cdf90f14d6 100644 --- a/apiserver/charms_test.go +++ b/apiserver/charms_test.go @@ -17,7 +17,7 @@ import ( "os" "path/filepath" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" diff --git a/apiserver/common/charms/appcharminfo_test.go b/apiserver/common/charms/appcharminfo_test.go index a817b9f7fe6..295ea84a6c6 100644 --- a/apiserver/common/charms/appcharminfo_test.go +++ b/apiserver/common/charms/appcharminfo_test.go @@ -6,9 +6,9 @@ package charms_test import ( "context" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/apiserver/common/charms/charminfo_test.go b/apiserver/common/charms/charminfo_test.go index 46c25b91fb2..de250bfac28 100644 --- a/apiserver/common/charms/charminfo_test.go +++ b/apiserver/common/charms/charminfo_test.go @@ -6,8 +6,8 @@ package charms_test import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/apiserver/common/charms/common.go b/apiserver/common/charms/common.go index d35c254a4ec..6c8657e0be2 100644 --- a/apiserver/common/charms/common.go +++ b/apiserver/common/charms/common.go @@ -6,9 +6,9 @@ package charms import ( "context" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/facade" diff --git a/apiserver/common/charms/mocks/mocks.go b/apiserver/common/charms/mocks/mocks.go index 78cd7b458a5..65340a3179e 100644 --- a/apiserver/common/charms/mocks/mocks.go +++ b/apiserver/common/charms/mocks/mocks.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charms "github.com/juju/juju/apiserver/common/charms" state "github.com/juju/juju/state" names "github.com/juju/names/v5" diff --git a/apiserver/common/crossmodel/interface.go b/apiserver/common/crossmodel/interface.go index cfd6538309f..5c9eab3c7e3 100644 --- a/apiserver/common/crossmodel/interface.go +++ b/apiserver/common/crossmodel/interface.go @@ -7,7 +7,7 @@ import ( "context" "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "gopkg.in/macaroon.v2" diff --git a/apiserver/common/firewall/firewall.go b/apiserver/common/firewall/firewall.go index c0699282f1e..996cebd42f6 100644 --- a/apiserver/common/firewall/firewall.go +++ b/apiserver/common/firewall/firewall.go @@ -4,8 +4,8 @@ package firewall import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" apiservererrors "github.com/juju/juju/apiserver/errors" diff --git a/apiserver/common/firewall/firewall_test.go b/apiserver/common/firewall/firewall_test.go index ca6f7857a97..a1565c1138b 100644 --- a/apiserver/common/firewall/firewall_test.go +++ b/apiserver/common/firewall/firewall_test.go @@ -4,7 +4,7 @@ package firewall_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gomock "go.uber.org/mock/gomock" diff --git a/apiserver/common/unitstatus.go b/apiserver/common/unitstatus.go index 377ca746b33..6e41e7aebf0 100644 --- a/apiserver/common/unitstatus.go +++ b/apiserver/common/unitstatus.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/status" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/apiserver/facades/agent/caasapplication/state.go b/apiserver/facades/agent/caasapplication/state.go index e78d62fe435..cbeebfd5acf 100644 --- a/apiserver/facades/agent/caasapplication/state.go +++ b/apiserver/facades/agent/caasapplication/state.go @@ -4,7 +4,7 @@ package caasapplication import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/version/v2" diff --git a/apiserver/facades/agent/instancemutater/instancemutater_test.go b/apiserver/facades/agent/instancemutater/instancemutater_test.go index 5e1ca3c274f..6924088b605 100644 --- a/apiserver/facades/agent/instancemutater/instancemutater_test.go +++ b/apiserver/facades/agent/instancemutater/instancemutater_test.go @@ -8,8 +8,8 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" coretesting "github.com/juju/testing" "go.uber.org/mock/gomock" diff --git a/apiserver/facades/agent/payloadshookcontext/unitfacade_test.go b/apiserver/facades/agent/payloadshookcontext/unitfacade_test.go index 72bbd965528..92d738a5a2a 100644 --- a/apiserver/facades/agent/payloadshookcontext/unitfacade_test.go +++ b/apiserver/facades/agent/payloadshookcontext/unitfacade_test.go @@ -6,8 +6,8 @@ package payloadshookcontext_test import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/agent/provisioner/interface.go b/apiserver/facades/agent/provisioner/interface.go index 0ac1027e5d6..cec4cc2e845 100644 --- a/apiserver/facades/agent/provisioner/interface.go +++ b/apiserver/facades/agent/provisioner/interface.go @@ -4,7 +4,7 @@ package provisioner import ( - jujucharm "github.com/juju/charm/v13" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/instance" diff --git a/apiserver/facades/agent/provisioner/mocks/package_mock.go b/apiserver/facades/agent/provisioner/mocks/package_mock.go index d5077c496dd..7cc8a6f076f 100644 --- a/apiserver/facades/agent/provisioner/mocks/package_mock.go +++ b/apiserver/facades/agent/provisioner/mocks/package_mock.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" set "github.com/juju/collections/set" provisioner "github.com/juju/juju/apiserver/facades/agent/provisioner" constraints "github.com/juju/juju/core/constraints" diff --git a/apiserver/facades/agent/provisioner/provisioner_test.go b/apiserver/facades/agent/provisioner/provisioner_test.go index c1e850c9252..4f37989b18e 100644 --- a/apiserver/facades/agent/provisioner/provisioner_test.go +++ b/apiserver/facades/agent/provisioner/provisioner_test.go @@ -9,8 +9,8 @@ import ( stdtesting "testing" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/proxy" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/agent/provisioner/shim.go b/apiserver/facades/agent/provisioner/shim.go index 25d055482fd..b58f8e66fb6 100644 --- a/apiserver/facades/agent/provisioner/shim.go +++ b/apiserver/facades/agent/provisioner/shim.go @@ -4,8 +4,8 @@ package provisioner import ( - jujucharm "github.com/juju/charm/v13" "github.com/juju/errors" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/network/containerizer" "github.com/juju/juju/state" diff --git a/apiserver/facades/agent/uniter/goal-state_test.go b/apiserver/facades/agent/uniter/goal-state_test.go index 41f429ff7ee..dbf1d6a38cd 100644 --- a/apiserver/facades/agent/uniter/goal-state_test.go +++ b/apiserver/facades/agent/uniter/goal-state_test.go @@ -7,8 +7,8 @@ import ( stdcontext "context" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/agent/uniter/networkinfo_test.go b/apiserver/facades/agent/uniter/networkinfo_test.go index e656b469673..57defc2c2c2 100644 --- a/apiserver/facades/agent/uniter/networkinfo_test.go +++ b/apiserver/facades/agent/uniter/networkinfo_test.go @@ -10,8 +10,8 @@ import ( "math/rand" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/retry" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/agent/uniter/subordinaterelationwatcher.go b/apiserver/facades/agent/uniter/subordinaterelationwatcher.go index c7c409b91df..f556c1f461a 100644 --- a/apiserver/facades/agent/uniter/subordinaterelationwatcher.go +++ b/apiserver/facades/agent/uniter/subordinaterelationwatcher.go @@ -4,9 +4,9 @@ package uniter import ( - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/worker/v4/catacomb" corelogger "github.com/juju/juju/core/logger" diff --git a/apiserver/facades/agent/uniter/uniter.go b/apiserver/facades/agent/uniter/uniter.go index 275a693476e..1b463710efe 100644 --- a/apiserver/facades/agent/uniter/uniter.go +++ b/apiserver/facades/agent/uniter/uniter.go @@ -10,10 +10,10 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/collections/transform" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/retry" diff --git a/apiserver/facades/agent/uniter/uniter_network_test.go b/apiserver/facades/agent/uniter/uniter_network_test.go index 63b1fb562fb..5901845ef20 100644 --- a/apiserver/facades/agent/uniter/uniter_network_test.go +++ b/apiserver/facades/agent/uniter/uniter_network_test.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/agent/uniter/uniter_test.go b/apiserver/facades/agent/uniter/uniter_test.go index 90e2160809e..ce4206ef01e 100644 --- a/apiserver/facades/agent/uniter/uniter_test.go +++ b/apiserver/facades/agent/uniter/uniter_test.go @@ -8,9 +8,9 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/client/application/application.go b/apiserver/facades/client/application/application.go index 8831f255ebc..3610deed417 100644 --- a/apiserver/facades/client/application/application.go +++ b/apiserver/facades/client/application/application.go @@ -11,8 +11,8 @@ import ( "reflect" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/schema" "github.com/juju/version/v2" diff --git a/apiserver/facades/client/application/application_test.go b/apiserver/facades/client/application/application_test.go index 9786e642177..7c30ce8f0dd 100644 --- a/apiserver/facades/client/application/application_test.go +++ b/apiserver/facades/client/application/application_test.go @@ -8,8 +8,8 @@ import ( "fmt" "regexp" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" diff --git a/apiserver/facades/client/application/application_unit_test.go b/apiserver/facades/client/application/application_unit_test.go index d774196df6f..2dcb4a0e77b 100644 --- a/apiserver/facades/client/application/application_unit_test.go +++ b/apiserver/facades/client/application/application_unit_test.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/assumes" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/assumes" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/client/application/backend.go b/apiserver/facades/client/application/backend.go index e2b7d436298..0d2130717c3 100644 --- a/apiserver/facades/client/application/backend.go +++ b/apiserver/facades/client/application/backend.go @@ -7,9 +7,9 @@ import ( "context" "time" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/schema" "github.com/juju/version/v2" diff --git a/apiserver/facades/client/application/deploy.go b/apiserver/facades/client/application/deploy.go index cd6b894cb47..5c5d6c88bcd 100644 --- a/apiserver/facades/client/application/deploy.go +++ b/apiserver/facades/client/application/deploy.go @@ -7,9 +7,9 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/assumes" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/assumes" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/common" diff --git a/apiserver/facades/client/application/deploy_test.go b/apiserver/facades/client/application/deploy_test.go index 956ac6a7193..5e06002db09 100644 --- a/apiserver/facades/client/application/deploy_test.go +++ b/apiserver/facades/client/application/deploy_test.go @@ -7,9 +7,9 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "gopkg.in/juju/environschema.v1" diff --git a/apiserver/facades/client/application/deployrepository.go b/apiserver/facades/client/application/deployrepository.go index 018d065056e..c1310f66757 100644 --- a/apiserver/facades/client/application/deployrepository.go +++ b/apiserver/facades/client/application/deployrepository.go @@ -9,11 +9,11 @@ import ( "strconv" "sync" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" jujuclock "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/kr/pretty" diff --git a/apiserver/facades/client/application/deployrepository_mocks_test.go b/apiserver/facades/client/application/deployrepository_mocks_test.go index a65e2188c78..0e3e7d83e92 100644 --- a/apiserver/facades/client/application/deployrepository_mocks_test.go +++ b/apiserver/facades/client/application/deployrepository_mocks_test.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - resource "github.com/juju/charm/v13/resource" + resource "github.com/juju/juju/internal/charm/resource" constraints "github.com/juju/juju/core/constraints" instance "github.com/juju/juju/core/instance" network "github.com/juju/juju/core/network" diff --git a/apiserver/facades/client/application/deployrepository_test.go b/apiserver/facades/client/application/deployrepository_test.go index 9662830623d..a599c954671 100644 --- a/apiserver/facades/client/application/deployrepository_test.go +++ b/apiserver/facades/client/application/deployrepository_test.go @@ -8,9 +8,9 @@ import ( "fmt" "reflect" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" diff --git a/apiserver/facades/client/application/get.go b/apiserver/facades/client/application/get.go index 37096061f4a..fc86d35d985 100644 --- a/apiserver/facades/client/application/get.go +++ b/apiserver/facades/client/application/get.go @@ -6,7 +6,7 @@ package application import ( "context" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/schema" "gopkg.in/juju/environschema.v1" diff --git a/apiserver/facades/client/application/get_test.go b/apiserver/facades/client/application/get_test.go index 9db91a04d22..590de5557e9 100644 --- a/apiserver/facades/client/application/get_test.go +++ b/apiserver/facades/client/application/get_test.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "gopkg.in/juju/environschema.v1" diff --git a/apiserver/facades/client/application/mocks/application_mock.go b/apiserver/facades/client/application/mocks/application_mock.go index 04985707f9a..469fe6b3842 100644 --- a/apiserver/facades/client/application/mocks/application_mock.go +++ b/apiserver/facades/client/application/mocks/application_mock.go @@ -14,8 +14,8 @@ import ( reflect "reflect" time "time" - charm "github.com/juju/charm/v13" - resource "github.com/juju/charm/v13/resource" + charm "github.com/juju/juju/internal/charm" + resource "github.com/juju/juju/internal/charm/resource" storagecommon "github.com/juju/juju/apiserver/common/storagecommon" application "github.com/juju/juju/apiserver/facades/client/application" config "github.com/juju/juju/core/config" diff --git a/apiserver/facades/client/application/mocks/lxdprofile_mock.go b/apiserver/facades/client/application/mocks/lxdprofile_mock.go index fa52f5e31f2..30aa48cbf71 100644 --- a/apiserver/facades/client/application/mocks/lxdprofile_mock.go +++ b/apiserver/facades/client/application/mocks/lxdprofile_mock.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/apiserver/facades/client/application/repository_mocks_test.go b/apiserver/facades/client/application/repository_mocks_test.go index cc940c46546..7571f7ed9a7 100644 --- a/apiserver/facades/client/application/repository_mocks_test.go +++ b/apiserver/facades/client/application/repository_mocks_test.go @@ -14,8 +14,8 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/charm/v13" - resource "github.com/juju/charm/v13/resource" + charm "github.com/juju/juju/internal/charm" + resource "github.com/juju/juju/internal/charm/resource" charm0 "github.com/juju/juju/core/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/apiserver/facades/client/application/updatebase_mocks_test.go b/apiserver/facades/client/application/updatebase_mocks_test.go index 748c5337ec5..8b01b080ad8 100644 --- a/apiserver/facades/client/application/updatebase_mocks_test.go +++ b/apiserver/facades/client/application/updatebase_mocks_test.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" base "github.com/juju/juju/core/base" config "github.com/juju/juju/core/config" constraints "github.com/juju/juju/core/constraints" diff --git a/apiserver/facades/client/application/updatebase_test.go b/apiserver/facades/client/application/updatebase_test.go index 21ff79599b5..273e5fe5851 100644 --- a/apiserver/facades/client/application/updatebase_test.go +++ b/apiserver/facades/client/application/updatebase_test.go @@ -6,8 +6,8 @@ package application import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/apiserver/facades/client/applicationoffers/applicationoffers_test.go b/apiserver/facades/client/applicationoffers/applicationoffers_test.go index dc20459077b..e510c145fe2 100644 --- a/apiserver/facades/client/applicationoffers/applicationoffers_test.go +++ b/apiserver/facades/client/applicationoffers/applicationoffers_test.go @@ -10,9 +10,9 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/client/applicationoffers/base_test.go b/apiserver/facades/client/applicationoffers/base_test.go index 83f039c0cbe..c6c3a1def8c 100644 --- a/apiserver/facades/client/applicationoffers/base_test.go +++ b/apiserver/facades/client/applicationoffers/base_test.go @@ -4,7 +4,7 @@ package applicationoffers_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jtesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/client/applicationoffers/mock_test.go b/apiserver/facades/client/applicationoffers/mock_test.go index 68314f6df6d..43498c1a30c 100644 --- a/apiserver/facades/client/applicationoffers/mock_test.go +++ b/apiserver/facades/client/applicationoffers/mock_test.go @@ -11,8 +11,8 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jtesting "github.com/juju/testing" "gopkg.in/macaroon.v2" diff --git a/apiserver/facades/client/bundle/bundle.go b/apiserver/facades/client/bundle/bundle.go index 103ddcc0275..afd86df714c 100644 --- a/apiserver/facades/client/bundle/bundle.go +++ b/apiserver/facades/client/bundle/bundle.go @@ -11,11 +11,11 @@ import ( "strconv" "strings" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/collections/set" "github.com/juju/description/v6" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "gopkg.in/yaml.v2" diff --git a/apiserver/facades/client/bundle/bundle_test.go b/apiserver/facades/client/bundle/bundle_test.go index 30273ca15a3..802c72c4a6b 100644 --- a/apiserver/facades/client/bundle/bundle_test.go +++ b/apiserver/facades/client/bundle/bundle_test.go @@ -8,9 +8,9 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/description/v6" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" diff --git a/apiserver/facades/client/bundle/mock_test.go b/apiserver/facades/client/bundle/mock_test.go index c186996da7b..f31eb8733c1 100644 --- a/apiserver/facades/client/bundle/mock_test.go +++ b/apiserver/facades/client/bundle/mock_test.go @@ -4,8 +4,8 @@ package bundle_test import ( - "github.com/juju/charm/v13" "github.com/juju/description/v6" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" "github.com/juju/juju/apiserver/facades/client/bundle" diff --git a/apiserver/facades/client/bundle/state.go b/apiserver/facades/client/bundle/state.go index 037d9cfb56f..ebc9bb09999 100644 --- a/apiserver/facades/client/bundle/state.go +++ b/apiserver/facades/client/bundle/state.go @@ -4,8 +4,8 @@ package bundle import ( - "github.com/juju/charm/v13" "github.com/juju/description/v6" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/state" diff --git a/apiserver/facades/client/charms/client.go b/apiserver/facades/client/charms/client.go index f894b6dcb80..8e4901eeee6 100644 --- a/apiserver/facades/client/charms/client.go +++ b/apiserver/facades/client/charms/client.go @@ -10,10 +10,10 @@ import ( "sync" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/collections/transform" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" apiresources "github.com/juju/juju/api/client/resources" diff --git a/apiserver/facades/client/charms/client_integration_test.go b/apiserver/facades/client/charms/client_integration_test.go index e025b642c11..12b72552cb8 100644 --- a/apiserver/facades/client/charms/client_integration_test.go +++ b/apiserver/facades/client/charms/client_integration_test.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/client/charms/client_test.go b/apiserver/facades/client/charms/client_test.go index 77e6940a79c..b31552a0add 100644 --- a/apiserver/facades/client/charms/client_test.go +++ b/apiserver/facades/client/charms/client_test.go @@ -8,8 +8,8 @@ import ( "fmt" "net/url" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/apiserver/facades/client/charms/clientnormalize_test.go b/apiserver/facades/client/charms/clientnormalize_test.go index 01d5d3d159a..ee5e915f6db 100644 --- a/apiserver/facades/client/charms/clientnormalize_test.go +++ b/apiserver/facades/client/charms/clientnormalize_test.go @@ -4,7 +4,7 @@ package charms import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/client/charms/conversions.go b/apiserver/facades/client/charms/conversions.go index bcc1b6e4c87..22fef6c1c43 100644 --- a/apiserver/facades/client/charms/conversions.go +++ b/apiserver/facades/client/charms/conversions.go @@ -4,8 +4,8 @@ package charms import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" diff --git a/apiserver/facades/client/charms/interface.go b/apiserver/facades/client/charms/interface.go index 3729271549a..2bab11a4260 100644 --- a/apiserver/facades/client/charms/interface.go +++ b/apiserver/facades/client/charms/interface.go @@ -4,8 +4,8 @@ package charms import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/apiserver/facades/client/charms/interfaces" "github.com/juju/juju/internal/charm/services" diff --git a/apiserver/facades/client/charms/interfaces/downloader.go b/apiserver/facades/client/charms/interfaces/downloader.go index 423f87d2c83..baa2c431e2e 100644 --- a/apiserver/facades/client/charms/interfaces/downloader.go +++ b/apiserver/facades/client/charms/interfaces/downloader.go @@ -6,7 +6,7 @@ package interfaces import ( "context" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" corecharm "github.com/juju/juju/core/charm" ) diff --git a/apiserver/facades/client/charms/mocks/repository.go b/apiserver/facades/client/charms/mocks/repository.go index dbb06e196a4..06b7d71a6d4 100644 --- a/apiserver/facades/client/charms/mocks/repository.go +++ b/apiserver/facades/client/charms/mocks/repository.go @@ -14,8 +14,8 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/charm/v13" - resource "github.com/juju/charm/v13/resource" + charm "github.com/juju/juju/internal/charm" + resource "github.com/juju/juju/internal/charm/resource" charm0 "github.com/juju/juju/core/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/apiserver/facades/client/charms/mocks/state_mock.go b/apiserver/facades/client/charms/mocks/state_mock.go index a48d76ab6f3..280a7c58332 100644 --- a/apiserver/facades/client/charms/mocks/state_mock.go +++ b/apiserver/facades/client/charms/mocks/state_mock.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" interfaces "github.com/juju/juju/apiserver/facades/client/charms/interfaces" charm0 "github.com/juju/juju/core/charm" constraints "github.com/juju/juju/core/constraints" diff --git a/apiserver/facades/client/client/api_test.go b/apiserver/facades/client/client/api_test.go index b45241ca02f..912694d99bb 100644 --- a/apiserver/facades/client/client/api_test.go +++ b/apiserver/facades/client/client/api_test.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/client/client/backend.go b/apiserver/facades/client/client/backend.go index d241a6b390a..49e6b1ccfed 100644 --- a/apiserver/facades/client/client/backend.go +++ b/apiserver/facades/client/client/backend.go @@ -6,8 +6,8 @@ package client import ( "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/names/v5" "github.com/juju/replicaset/v3" diff --git a/apiserver/facades/client/client/client_test.go b/apiserver/facades/client/client/client_test.go index da8f3dc0d05..69e696f97bd 100644 --- a/apiserver/facades/client/client/client_test.go +++ b/apiserver/facades/client/client/client_test.go @@ -7,8 +7,8 @@ import ( "context" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" jtesting "github.com/juju/testing" diff --git a/apiserver/facades/client/client/package_mock_test.go b/apiserver/facades/client/client/package_mock_test.go index f97a0df47c5..baf3dfe4718 100644 --- a/apiserver/facades/client/client/package_mock_test.go +++ b/apiserver/facades/client/client/package_mock_test.go @@ -13,7 +13,7 @@ import ( reflect "reflect" time "time" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" client "github.com/juju/juju/apiserver/facades/client/client" crossmodel "github.com/juju/juju/core/crossmodel" permission "github.com/juju/juju/core/permission" diff --git a/apiserver/facades/client/client/status.go b/apiserver/facades/client/client/status.go index b3a6b3dc829..141ef872d2d 100644 --- a/apiserver/facades/client/client/status.go +++ b/apiserver/facades/client/client/status.go @@ -9,9 +9,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/common" diff --git a/apiserver/facades/client/client/status_test.go b/apiserver/facades/client/client/status_test.go index 68728c4e0e8..0ad5f2391b2 100644 --- a/apiserver/facades/client/client/status_test.go +++ b/apiserver/facades/client/client/status_test.go @@ -8,8 +8,8 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/apiserver/facades/client/controller/backend.go b/apiserver/facades/client/controller/backend.go index 88605ff0c0a..bbd2a895d7a 100644 --- a/apiserver/facades/client/controller/backend.go +++ b/apiserver/facades/client/controller/backend.go @@ -4,7 +4,7 @@ package controller import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/controller/mocks/state_mock.go b/apiserver/facades/client/controller/mocks/state_mock.go index 2205c2b9a08..edd18adc9b1 100644 --- a/apiserver/facades/client/controller/mocks/state_mock.go +++ b/apiserver/facades/client/controller/mocks/state_mock.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" controller "github.com/juju/juju/apiserver/facades/client/controller" state "github.com/juju/juju/state" gomock "go.uber.org/mock/gomock" diff --git a/apiserver/facades/client/machinemanager/machinemanager_test.go b/apiserver/facades/client/machinemanager/machinemanager_test.go index 134a4cd4ccd..5c4f0edd335 100644 --- a/apiserver/facades/client/machinemanager/machinemanager_test.go +++ b/apiserver/facades/client/machinemanager/machinemanager_test.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/client/machinemanager/package_mock_test.go b/apiserver/facades/client/machinemanager/package_mock_test.go index 90a18704cc6..805cb0c5725 100644 --- a/apiserver/facades/client/machinemanager/package_mock_test.go +++ b/apiserver/facades/client/machinemanager/package_mock_test.go @@ -14,7 +14,7 @@ import ( reflect "reflect" time "time" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" storagecommon "github.com/juju/juju/apiserver/common/storagecommon" machinemanager "github.com/juju/juju/apiserver/facades/client/machinemanager" controller "github.com/juju/juju/controller" diff --git a/apiserver/facades/client/machinemanager/state.go b/apiserver/facades/client/machinemanager/state.go index 932cf5a51db..84a11104a44 100644 --- a/apiserver/facades/client/machinemanager/state.go +++ b/apiserver/facades/client/machinemanager/state.go @@ -6,8 +6,8 @@ package machinemanager import ( "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/common/storagecommon" diff --git a/apiserver/facades/client/machinemanager/upgradebase_test.go b/apiserver/facades/client/machinemanager/upgradebase_test.go index 0a1771020b8..59e36ee8aa3 100644 --- a/apiserver/facades/client/machinemanager/upgradebase_test.go +++ b/apiserver/facades/client/machinemanager/upgradebase_test.go @@ -6,8 +6,8 @@ package machinemanager_test import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/client/modelgeneration/interface.go b/apiserver/facades/client/modelgeneration/interface.go index 1d30a1ed708..503e7ab6e92 100644 --- a/apiserver/facades/client/modelgeneration/interface.go +++ b/apiserver/facades/client/modelgeneration/interface.go @@ -4,7 +4,7 @@ package modelgeneration import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/settings" diff --git a/apiserver/facades/client/modelgeneration/mocks/package_mock.go b/apiserver/facades/client/modelgeneration/mocks/package_mock.go index a4b977e0518..dcb43fc9520 100644 --- a/apiserver/facades/client/modelgeneration/mocks/package_mock.go +++ b/apiserver/facades/client/modelgeneration/mocks/package_mock.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" modelgeneration "github.com/juju/juju/apiserver/facades/client/modelgeneration" settings "github.com/juju/juju/core/settings" names "github.com/juju/names/v5" diff --git a/apiserver/facades/client/modelgeneration/shim.go b/apiserver/facades/client/modelgeneration/shim.go index 53ae92ff3e1..be4f5450f55 100644 --- a/apiserver/facades/client/modelgeneration/shim.go +++ b/apiserver/facades/client/modelgeneration/shim.go @@ -4,8 +4,8 @@ package modelgeneration import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/payloads/facade_test.go b/apiserver/facades/client/payloads/facade_test.go index 0c7db4f1622..97b8745bb95 100644 --- a/apiserver/facades/client/payloads/facade_test.go +++ b/apiserver/facades/client/payloads/facade_test.go @@ -6,8 +6,8 @@ package payloads_test import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/client/resources/base_test.go b/apiserver/facades/client/resources/base_test.go index bb32eef1366..d90c6d85b20 100644 --- a/apiserver/facades/client/resources/base_test.go +++ b/apiserver/facades/client/resources/base_test.go @@ -6,7 +6,7 @@ package resources_test import ( "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/apiserver/facades/client/resources/facade.go b/apiserver/facades/client/resources/facade.go index 19a9871dd45..c86660f9b15 100644 --- a/apiserver/facades/client/resources/facade.go +++ b/apiserver/facades/client/resources/facade.go @@ -6,9 +6,9 @@ package resources import ( "context" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" apiresources "github.com/juju/juju/api/client/resources" diff --git a/apiserver/facades/client/resources/mocks/backend.go b/apiserver/facades/client/resources/mocks/backend.go index 28c35d6f003..ba33d2513e3 100644 --- a/apiserver/facades/client/resources/mocks/backend.go +++ b/apiserver/facades/client/resources/mocks/backend.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - resource "github.com/juju/charm/v13/resource" + resource "github.com/juju/juju/internal/charm/resource" charm "github.com/juju/juju/core/charm" resources "github.com/juju/juju/core/resources" gomock "go.uber.org/mock/gomock" diff --git a/apiserver/facades/client/resources/repository.go b/apiserver/facades/client/resources/repository.go index b349489addd..0dbcdc02f47 100644 --- a/apiserver/facades/client/resources/repository.go +++ b/apiserver/facades/client/resources/repository.go @@ -6,7 +6,7 @@ package resources import ( "context" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" corecharm "github.com/juju/juju/core/charm" ) diff --git a/apiserver/facades/client/resources/server_addpending_test.go b/apiserver/facades/client/resources/server_addpending_test.go index 50027eccfe6..1458fff8527 100644 --- a/apiserver/facades/client/resources/server_addpending_test.go +++ b/apiserver/facades/client/resources/server_addpending_test.go @@ -6,9 +6,9 @@ package resources_test import ( "context" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/apiserver/facades/client/resources/server_listresources_test.go b/apiserver/facades/client/resources/server_listresources_test.go index a7b43fa01a3..80be1f8fbff 100644 --- a/apiserver/facades/client/resources/server_listresources_test.go +++ b/apiserver/facades/client/resources/server_listresources_test.go @@ -6,8 +6,8 @@ package resources_test import ( "context" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/client/resources/server_test.go b/apiserver/facades/client/resources/server_test.go index 94428f36963..1b6ca32104c 100644 --- a/apiserver/facades/client/resources/server_test.go +++ b/apiserver/facades/client/resources/server_test.go @@ -4,7 +4,7 @@ package resources_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/client/storage/mock_test.go b/apiserver/facades/client/storage/mock_test.go index e5a22e651ea..3e4d5dbd2cb 100644 --- a/apiserver/facades/client/storage/mock_test.go +++ b/apiserver/facades/client/storage/mock_test.go @@ -7,8 +7,8 @@ import ( "context" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/facades/client/storage" diff --git a/apiserver/facades/controller/caasapplicationprovisioner/mock_test.go b/apiserver/facades/controller/caasapplicationprovisioner/mock_test.go index d9188655f2b..381552d592e 100644 --- a/apiserver/facades/controller/caasapplicationprovisioner/mock_test.go +++ b/apiserver/facades/controller/caasapplicationprovisioner/mock_test.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" "gopkg.in/tomb.v2" diff --git a/apiserver/facades/controller/caasapplicationprovisioner/provisioner.go b/apiserver/facades/controller/caasapplicationprovisioner/provisioner.go index 594d2f51e41..c257bf09b24 100644 --- a/apiserver/facades/controller/caasapplicationprovisioner/provisioner.go +++ b/apiserver/facades/controller/caasapplicationprovisioner/provisioner.go @@ -11,10 +11,10 @@ import ( "sort" "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "gopkg.in/yaml.v2" diff --git a/apiserver/facades/controller/caasapplicationprovisioner/provisioner_test.go b/apiserver/facades/controller/caasapplicationprovisioner/provisioner_test.go index 4a4b2d37f4f..89d90457d0f 100644 --- a/apiserver/facades/controller/caasapplicationprovisioner/provisioner_test.go +++ b/apiserver/facades/controller/caasapplicationprovisioner/provisioner_test.go @@ -7,10 +7,10 @@ import ( "context" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/clock" "github.com/juju/clock/testclock" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/apiserver/facades/controller/caasapplicationprovisioner/state.go b/apiserver/facades/controller/caasapplicationprovisioner/state.go index 5375cd62b19..c83ee61b88b 100644 --- a/apiserver/facades/controller/caasapplicationprovisioner/state.go +++ b/apiserver/facades/controller/caasapplicationprovisioner/state.go @@ -8,7 +8,7 @@ import ( "io" "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/controller" diff --git a/apiserver/facades/controller/caasfirewaller/firewaller_test.go b/apiserver/facades/controller/caasfirewaller/firewaller_test.go index bf403742e70..9689988c3bc 100644 --- a/apiserver/facades/controller/caasfirewaller/firewaller_test.go +++ b/apiserver/facades/controller/caasfirewaller/firewaller_test.go @@ -6,7 +6,7 @@ package caasfirewaller_test import ( "context" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" diff --git a/apiserver/facades/controller/caasfirewaller/mock_test.go b/apiserver/facades/controller/caasfirewaller/mock_test.go index bbe713cbbcb..203ed85854d 100644 --- a/apiserver/facades/controller/caasfirewaller/mock_test.go +++ b/apiserver/facades/controller/caasfirewaller/mock_test.go @@ -4,7 +4,7 @@ package caasfirewaller_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" diff --git a/apiserver/facades/controller/charmdownloader/charmdownloader.go b/apiserver/facades/controller/charmdownloader/charmdownloader.go index 6f52de3986f..50420ab9c22 100644 --- a/apiserver/facades/controller/charmdownloader/charmdownloader.go +++ b/apiserver/facades/controller/charmdownloader/charmdownloader.go @@ -7,10 +7,10 @@ import ( "context" "sync" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/errors" "github.com/juju/http/v2" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" apiservererrors "github.com/juju/juju/apiserver/errors" diff --git a/apiserver/facades/controller/charmdownloader/charmdownloader_test.go b/apiserver/facades/controller/charmdownloader/charmdownloader_test.go index 2b0eedb7703..644a5da9b5e 100644 --- a/apiserver/facades/controller/charmdownloader/charmdownloader_test.go +++ b/apiserver/facades/controller/charmdownloader/charmdownloader_test.go @@ -8,9 +8,9 @@ import ( "net/http" "time" - "github.com/juju/charm/v13" "github.com/juju/clock/testclock" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/apiserver/facades/controller/charmdownloader/interfaces.go b/apiserver/facades/controller/charmdownloader/interfaces.go index ac0938372df..1daba2f04f2 100644 --- a/apiserver/facades/controller/charmdownloader/interfaces.go +++ b/apiserver/facades/controller/charmdownloader/interfaces.go @@ -6,7 +6,7 @@ package charmdownloader import ( "context" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/worker/v4" corecharm "github.com/juju/juju/core/charm" diff --git a/apiserver/facades/controller/charmdownloader/mocks/mocks.go b/apiserver/facades/controller/charmdownloader/mocks/mocks.go index a7838a43278..2277e8cb096 100644 --- a/apiserver/facades/controller/charmdownloader/mocks/mocks.go +++ b/apiserver/facades/controller/charmdownloader/mocks/mocks.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charmdownloader "github.com/juju/juju/apiserver/facades/controller/charmdownloader" charm0 "github.com/juju/juju/core/charm" status "github.com/juju/juju/core/status" diff --git a/apiserver/facades/controller/charmrevisionupdater/charmhub.go b/apiserver/facades/controller/charmrevisionupdater/charmhub.go index 01fd7115cfd..611a35cbf02 100644 --- a/apiserver/facades/controller/charmrevisionupdater/charmhub.go +++ b/apiserver/facades/controller/charmrevisionupdater/charmhub.go @@ -9,8 +9,8 @@ import ( "context" "time" - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/core/charm/metrics" corelogger "github.com/juju/juju/core/logger" diff --git a/apiserver/facades/controller/charmrevisionupdater/interface.go b/apiserver/facades/controller/charmrevisionupdater/interface.go index 5d5005e81df..0a9d6de240b 100644 --- a/apiserver/facades/controller/charmrevisionupdater/interface.go +++ b/apiserver/facades/controller/charmrevisionupdater/interface.go @@ -4,8 +4,8 @@ package charmrevisionupdater import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/common" diff --git a/apiserver/facades/controller/charmrevisionupdater/interface_test.go b/apiserver/facades/controller/charmrevisionupdater/interface_test.go index 25b30615bd9..b88d1baf4cd 100644 --- a/apiserver/facades/controller/charmrevisionupdater/interface_test.go +++ b/apiserver/facades/controller/charmrevisionupdater/interface_test.go @@ -4,8 +4,8 @@ package charmrevisionupdater_test import ( - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/apiserver/facades/controller/charmrevisionupdater/mocks/mocks.go b/apiserver/facades/controller/charmrevisionupdater/mocks/mocks.go index 3a394ee45e4..376f51d84b8 100644 --- a/apiserver/facades/controller/charmrevisionupdater/mocks/mocks.go +++ b/apiserver/facades/controller/charmrevisionupdater/mocks/mocks.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charmrevisionupdater "github.com/juju/juju/apiserver/facades/controller/charmrevisionupdater" metrics "github.com/juju/juju/core/charm/metrics" objectstore "github.com/juju/juju/core/objectstore" diff --git a/apiserver/facades/controller/charmrevisionupdater/updater.go b/apiserver/facades/controller/charmrevisionupdater/updater.go index d8e40889c3d..1f7dbb5c39c 100644 --- a/apiserver/facades/controller/charmrevisionupdater/updater.go +++ b/apiserver/facades/controller/charmrevisionupdater/updater.go @@ -9,11 +9,11 @@ import ( "strings" "time" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" apiservererrors "github.com/juju/juju/apiserver/errors" charmmetrics "github.com/juju/juju/core/charm/metrics" diff --git a/apiserver/facades/controller/charmrevisionupdater/updater_test.go b/apiserver/facades/controller/charmrevisionupdater/updater_test.go index 7113924af33..b8dff2b0507 100644 --- a/apiserver/facades/controller/charmrevisionupdater/updater_test.go +++ b/apiserver/facades/controller/charmrevisionupdater/updater_test.go @@ -7,10 +7,10 @@ import ( "context" "time" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/resource" "github.com/juju/clock" "github.com/juju/clock/testclock" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/apiserver/facades/controller/crossmodelrelations/crossmodelrelations.go b/apiserver/facades/controller/crossmodelrelations/crossmodelrelations.go index a5c172918d8..616b5adaf72 100644 --- a/apiserver/facades/controller/crossmodelrelations/crossmodelrelations.go +++ b/apiserver/facades/controller/crossmodelrelations/crossmodelrelations.go @@ -10,8 +10,8 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/worker/v4" "github.com/kr/pretty" diff --git a/apiserver/facades/controller/crossmodelrelations/crossmodelrelations_test.go b/apiserver/facades/controller/crossmodelrelations/crossmodelrelations_test.go index 8acc8062eb4..af2c666452b 100644 --- a/apiserver/facades/controller/crossmodelrelations/crossmodelrelations_test.go +++ b/apiserver/facades/controller/crossmodelrelations/crossmodelrelations_test.go @@ -12,8 +12,8 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" - "github.com/juju/charm/v13" "github.com/juju/clock" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/facades/controller/remoterelations/remoterelations_test.go b/apiserver/facades/controller/remoterelations/remoterelations_test.go index 38ce99d8b6b..ff46a531ac5 100644 --- a/apiserver/facades/controller/remoterelations/remoterelations_test.go +++ b/apiserver/facades/controller/remoterelations/remoterelations_test.go @@ -6,8 +6,8 @@ package remoterelations_test import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/mocks/resources_mock.go b/apiserver/mocks/resources_mock.go index edb8796676c..c78e2055ee2 100644 --- a/apiserver/mocks/resources_mock.go +++ b/apiserver/mocks/resources_mock.go @@ -14,7 +14,7 @@ import ( reflect "reflect" time "time" - resource "github.com/juju/charm/v13/resource" + resource "github.com/juju/juju/internal/charm/resource" resources "github.com/juju/juju/core/resources" state "github.com/juju/juju/state" gomock "go.uber.org/mock/gomock" diff --git a/apiserver/objects.go b/apiserver/objects.go index 180b249b953..d03054d6d13 100644 --- a/apiserver/objects.go +++ b/apiserver/objects.go @@ -12,8 +12,8 @@ import ( "os" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/utils/v4" "github.com/juju/juju/core/objectstore" diff --git a/apiserver/objects_test.go b/apiserver/objects_test.go index 689af797b1e..9fe8dabc946 100644 --- a/apiserver/objects_test.go +++ b/apiserver/objects_test.go @@ -14,7 +14,7 @@ import ( "os" "strings" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" diff --git a/apiserver/resources.go b/apiserver/resources.go index 2d6ab255b0e..f0338659b49 100644 --- a/apiserver/resources.go +++ b/apiserver/resources.go @@ -12,8 +12,8 @@ import ( "path" "strconv" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" api "github.com/juju/juju/api/client/resources" diff --git a/apiserver/resources_mig.go b/apiserver/resources_mig.go index ed8ad3e678c..bbf1cabbb5e 100644 --- a/apiserver/resources_mig.go +++ b/apiserver/resources_mig.go @@ -9,8 +9,8 @@ import ( "net/url" "strconv" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/resources" diff --git a/apiserver/resources_mig_test.go b/apiserver/resources_mig_test.go index c4ae4c5eb69..b960e6519d7 100644 --- a/apiserver/resources_mig_test.go +++ b/apiserver/resources_mig_test.go @@ -13,7 +13,7 @@ import ( "strings" "time" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/apiserver/resources_test.go b/apiserver/resources_test.go index 9c61fc740c0..0a98b8ac219 100644 --- a/apiserver/resources_test.go +++ b/apiserver/resources_test.go @@ -13,9 +13,9 @@ import ( "strings" "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/collections/set" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/apiserver/testing/application.go b/apiserver/testing/application.go index 89da94342ca..4f4a956a16c 100644 --- a/apiserver/testing/application.go +++ b/apiserver/testing/application.go @@ -4,7 +4,7 @@ package testing import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/caas/kubernetes/provider/bootstrap.go b/caas/kubernetes/provider/bootstrap.go index 689a1aaa9de..e5de3236e72 100644 --- a/caas/kubernetes/provider/bootstrap.go +++ b/caas/kubernetes/provider/bootstrap.go @@ -9,10 +9,10 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/featureflag" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" "github.com/juju/retry" diff --git a/caas/kubernetes/provider/providerconfig.go b/caas/kubernetes/provider/providerconfig.go index 69beb0c9956..8493611d46f 100644 --- a/caas/kubernetes/provider/providerconfig.go +++ b/caas/kubernetes/provider/providerconfig.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/schema" "github.com/juju/version/v2" "gopkg.in/juju/environschema.v1" diff --git a/cmd/juju/action/common.go b/cmd/juju/action/common.go index 0eb851d879f..6fcbda5e722 100644 --- a/cmd/juju/action/common.go +++ b/cmd/juju/action/common.go @@ -16,12 +16,12 @@ import ( "time" "github.com/juju/ansiterm" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/mattn/go-isatty" "gopkg.in/yaml.v2" diff --git a/cmd/juju/agree/agree/agree.go b/cmd/juju/agree/agree/agree.go index b5f2409f07f..e70da75069c 100644 --- a/cmd/juju/agree/agree/agree.go +++ b/cmd/juju/agree/agree/agree.go @@ -12,10 +12,10 @@ import ( "os/exec" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" "github.com/juju/terms-client/v2/api" "github.com/juju/terms-client/v2/api/wireformat" diff --git a/cmd/juju/application/bundle/bundle.go b/cmd/juju/application/bundle/bundle.go index f6baf89d012..9a5f75fc2b2 100644 --- a/cmd/juju/application/bundle/bundle.go +++ b/cmd/juju/application/bundle/bundle.go @@ -11,8 +11,8 @@ import ( "sort" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "gopkg.in/yaml.v3" @@ -254,7 +254,6 @@ func applicationConfigValue(key string, valueMap interface{}) (interface{}, erro // processing the bundle. They are for informational purposes and do // not require failing the bundle deployment. func ComposeAndVerifyBundle(base BundleDataSource, pathToOverlays []string) (*charm.BundleData, []error, error) { - verifyConstraints := func(s string) error { _, err := constraints.Parse(s) return err diff --git a/cmd/juju/application/bundle/bundle_test.go b/cmd/juju/application/bundle/bundle_test.go index 5e4dd230277..5be1df7aba1 100644 --- a/cmd/juju/application/bundle/bundle_test.go +++ b/cmd/juju/application/bundle/bundle_test.go @@ -8,8 +8,8 @@ import ( "path/filepath" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/cmd/juju/application/bundle/interface.go b/cmd/juju/application/bundle/interface.go index 8c36161a661..50911dca2f2 100644 --- a/cmd/juju/application/bundle/interface.go +++ b/cmd/juju/application/bundle/interface.go @@ -4,7 +4,7 @@ package bundle import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/constraints" "github.com/juju/juju/rpc/params" diff --git a/cmd/juju/application/bundle/mocks/bundledatasource_mock.go b/cmd/juju/application/bundle/mocks/bundledatasource_mock.go index b6807f46431..a1db2c3c40c 100644 --- a/cmd/juju/application/bundle/mocks/bundledatasource_mock.go +++ b/cmd/juju/application/bundle/mocks/bundledatasource_mock.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/cmd/juju/application/bundle_test.go b/cmd/juju/application/bundle_test.go index 280a02ddc4b..7a91f290d67 100644 --- a/cmd/juju/application/bundle_test.go +++ b/cmd/juju/application/bundle_test.go @@ -9,9 +9,9 @@ import ( "path/filepath" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/application/deploy.go b/cmd/juju/application/deploy.go index 2002c3347c4..d136e2a9b5e 100644 --- a/cmd/juju/application/deploy.go +++ b/cmd/juju/application/deploy.go @@ -7,11 +7,11 @@ import ( "strconv" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/version/v2" diff --git a/cmd/juju/application/deploy_test.go b/cmd/juju/application/deploy_test.go index 3afd8873a4b..c09723f3fff 100644 --- a/cmd/juju/application/deploy_test.go +++ b/cmd/juju/application/deploy_test.go @@ -14,13 +14,13 @@ import ( "strings" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/collections/transform" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/loggo/v2" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/cmd/juju/application/deployer/bundle.go b/cmd/juju/application/deployer/bundle.go index 6b800fc8278..c769cbdc927 100644 --- a/cmd/juju/application/deployer/bundle.go +++ b/cmd/juju/application/deployer/bundle.go @@ -7,10 +7,10 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/cmd/juju/application/bundle" diff --git a/cmd/juju/application/deployer/bundle_test.go b/cmd/juju/application/deployer/bundle_test.go index 5e7d4310e42..d91d3a4b89b 100644 --- a/cmd/juju/application/deployer/bundle_test.go +++ b/cmd/juju/application/deployer/bundle_test.go @@ -4,7 +4,7 @@ package deployer import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/application/deployer/bundlehandler.go b/cmd/juju/application/deployer/bundlehandler.go index 502c4aa9a25..04d11ca0867 100644 --- a/cmd/juju/application/deployer/bundlehandler.go +++ b/cmd/juju/application/deployer/bundlehandler.go @@ -12,12 +12,12 @@ import ( "strings" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" jujuclock "github.com/juju/clock" "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/kr/pretty" "gopkg.in/yaml.v2" diff --git a/cmd/juju/application/deployer/bundlehandler_test.go b/cmd/juju/application/deployer/bundlehandler_test.go index 8c0f1b25dc9..ee4325ee6b2 100644 --- a/cmd/juju/application/deployer/bundlehandler_test.go +++ b/cmd/juju/application/deployer/bundlehandler_test.go @@ -10,12 +10,12 @@ import ( "strings" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/collections/transform" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" diff --git a/cmd/juju/application/deployer/charm.go b/cmd/juju/application/deployer/charm.go index 29b7f33cf8a..fbf6ffad41c 100644 --- a/cmd/juju/application/deployer/charm.go +++ b/cmd/juju/application/deployer/charm.go @@ -7,11 +7,11 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" jujuclock "github.com/juju/clock" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/api/client/application" applicationapi "github.com/juju/juju/api/client/application" diff --git a/cmd/juju/application/deployer/charm_test.go b/cmd/juju/application/deployer/charm_test.go index 763e52eefa5..1a7b1dc53a0 100644 --- a/cmd/juju/application/deployer/charm_test.go +++ b/cmd/juju/application/deployer/charm_test.go @@ -7,13 +7,13 @@ import ( "bytes" "context" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/clock" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/cmd/juju/application/deployer/deployer.go b/cmd/juju/application/deployer/deployer.go index 2209abdf67c..7a2d8ae2bc1 100644 --- a/cmd/juju/application/deployer/deployer.go +++ b/cmd/juju/application/deployer/deployer.go @@ -12,11 +12,11 @@ import ( "sort" "strings" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" jujuclock "github.com/juju/clock" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/api/client/application" commoncharm "github.com/juju/juju/api/common/charm" diff --git a/cmd/juju/application/deployer/deployer_test.go b/cmd/juju/application/deployer/deployer_test.go index d63e4980f7a..6b47edebeaa 100644 --- a/cmd/juju/application/deployer/deployer_test.go +++ b/cmd/juju/application/deployer/deployer_test.go @@ -9,10 +9,10 @@ import ( "os" "path/filepath" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/cmd/juju/application/deployer/interface.go b/cmd/juju/application/deployer/interface.go index 44e1bd0d676..606fae21ba7 100644 --- a/cmd/juju/application/deployer/interface.go +++ b/cmd/juju/application/deployer/interface.go @@ -7,9 +7,9 @@ import ( "context" "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/cmd/v4" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/api" diff --git a/cmd/juju/application/deployer/lxdprofile.go b/cmd/juju/application/deployer/lxdprofile.go index 27fa446fc10..b9020ef85a9 100644 --- a/cmd/juju/application/deployer/lxdprofile.go +++ b/cmd/juju/application/deployer/lxdprofile.go @@ -4,7 +4,7 @@ package deployer import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" apicharms "github.com/juju/juju/api/common/charms" "github.com/juju/juju/core/lxdprofile" diff --git a/cmd/juju/application/deployer/mocks/charm_mock.go b/cmd/juju/application/deployer/mocks/charm_mock.go index 9e29035e88b..5d6d8c6886f 100644 --- a/cmd/juju/application/deployer/mocks/charm_mock.go +++ b/cmd/juju/application/deployer/mocks/charm_mock.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/cmd/juju/application/deployer/mocks/deploy_mock.go b/cmd/juju/application/deployer/mocks/deploy_mock.go index 905e6286e34..64dafc0a3d3 100644 --- a/cmd/juju/application/deployer/mocks/deploy_mock.go +++ b/cmd/juju/application/deployer/mocks/deploy_mock.go @@ -15,8 +15,8 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/charm/v13" - resource "github.com/juju/charm/v13/resource" + charm "github.com/juju/juju/internal/charm" + resource "github.com/juju/juju/internal/charm/resource" cmd "github.com/juju/cmd/v4" api "github.com/juju/juju/api" base "github.com/juju/juju/api/base" diff --git a/cmd/juju/application/deployer/mocks/resolver_mock.go b/cmd/juju/application/deployer/mocks/resolver_mock.go index 9794e39deeb..f3c15e98ee1 100644 --- a/cmd/juju/application/deployer/mocks/resolver_mock.go +++ b/cmd/juju/application/deployer/mocks/resolver_mock.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charm0 "github.com/juju/juju/api/common/charm" base "github.com/juju/juju/core/base" gomock "go.uber.org/mock/gomock" diff --git a/cmd/juju/application/deployer/resource.go b/cmd/juju/application/deployer/resource.go index 4289f763a5c..184fd7140ce 100644 --- a/cmd/juju/application/deployer/resource.go +++ b/cmd/juju/application/deployer/resource.go @@ -7,8 +7,8 @@ import ( "context" "strconv" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/api/base" "github.com/juju/juju/api/client/application" diff --git a/cmd/juju/application/diffbundle.go b/cmd/juju/application/diffbundle.go index 7da186b68b9..579ec2cd45f 100644 --- a/cmd/juju/application/diffbundle.go +++ b/cmd/juju/application/diffbundle.go @@ -10,10 +10,10 @@ import ( "path/filepath" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" "gopkg.in/yaml.v2" "github.com/juju/juju/api/base" diff --git a/cmd/juju/application/diffbundle_test.go b/cmd/juju/application/diffbundle_test.go index 7a19491125a..df66d590895 100644 --- a/cmd/juju/application/diffbundle_test.go +++ b/cmd/juju/application/diffbundle_test.go @@ -11,10 +11,10 @@ import ( "reflect" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/application/export_test.go b/cmd/juju/application/export_test.go index 0357a691d07..6a5b42f7e3d 100644 --- a/cmd/juju/application/export_test.go +++ b/cmd/juju/application/export_test.go @@ -4,8 +4,8 @@ package application import ( - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/api" "github.com/juju/juju/api/base" diff --git a/cmd/juju/application/refresh.go b/cmd/juju/application/refresh.go index e82396995fd..c26a7d67b22 100644 --- a/cmd/juju/application/refresh.go +++ b/cmd/juju/application/refresh.go @@ -8,11 +8,11 @@ import ( "fmt" "strconv" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api" diff --git a/cmd/juju/application/refresh_test.go b/cmd/juju/application/refresh_test.go index 79bad1d773f..e5469f93f57 100644 --- a/cmd/juju/application/refresh_test.go +++ b/cmd/juju/application/refresh_test.go @@ -12,11 +12,11 @@ import ( "path/filepath" "strings" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/cmd/juju/application/refresher/charm_mock_test.go b/cmd/juju/application/refresher/charm_mock_test.go index 63a3887a6e1..302ad82befb 100644 --- a/cmd/juju/application/refresher/charm_mock_test.go +++ b/cmd/juju/application/refresher/charm_mock_test.go @@ -12,7 +12,7 @@ package refresher import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/cmd/juju/application/refresher/interface.go b/cmd/juju/application/refresher/interface.go index ffa7931ad15..6d1d6fdcb17 100644 --- a/cmd/juju/application/refresher/interface.go +++ b/cmd/juju/application/refresher/interface.go @@ -4,7 +4,7 @@ package refresher import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/core/base" diff --git a/cmd/juju/application/refresher/refresher.go b/cmd/juju/application/refresher/refresher.go index 797f7ed07f1..8020e034098 100644 --- a/cmd/juju/application/refresher/refresher.go +++ b/cmd/juju/application/refresher/refresher.go @@ -8,10 +8,10 @@ import ( "os" "strings" - "github.com/juju/charm/v13" jujuclock "github.com/juju/clock" "github.com/juju/collections/transform" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/cmd/juju/application/store" diff --git a/cmd/juju/application/refresher/refresher_mock_test.go b/cmd/juju/application/refresher/refresher_mock_test.go index ddd92e6cf8b..a9e39265a8c 100644 --- a/cmd/juju/application/refresher/refresher_mock_test.go +++ b/cmd/juju/application/refresher/refresher_mock_test.go @@ -12,7 +12,7 @@ package refresher import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charm0 "github.com/juju/juju/api/common/charm" base "github.com/juju/juju/core/base" gomock "go.uber.org/mock/gomock" diff --git a/cmd/juju/application/refresher/refresher_test.go b/cmd/juju/application/refresher/refresher_test.go index 58a10d99c38..1ed95aa32d1 100644 --- a/cmd/juju/application/refresher/refresher_test.go +++ b/cmd/juju/application/refresher/refresher_test.go @@ -7,8 +7,8 @@ import ( "fmt" "os" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/cmd/juju/application/refresher/store_mock_test.go b/cmd/juju/application/refresher/store_mock_test.go index 2a1f0df80d0..9b9943e19f3 100644 --- a/cmd/juju/application/refresher/store_mock_test.go +++ b/cmd/juju/application/refresher/store_mock_test.go @@ -12,7 +12,7 @@ package refresher import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charm0 "github.com/juju/juju/api/common/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/cmd/juju/application/store/charmadaptor.go b/cmd/juju/application/store/charmadaptor.go index db415d51e9c..9479d5ce7f5 100644 --- a/cmd/juju/application/store/charmadaptor.go +++ b/cmd/juju/application/store/charmadaptor.go @@ -7,8 +7,8 @@ import ( "context" "net/url" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" apicharm "github.com/juju/juju/api/client/charms" commoncharm "github.com/juju/juju/api/common/charm" diff --git a/cmd/juju/application/store/charmadaptor_test.go b/cmd/juju/application/store/charmadaptor_test.go index e8b3f99deeb..6f33e676190 100644 --- a/cmd/juju/application/store/charmadaptor_test.go +++ b/cmd/juju/application/store/charmadaptor_test.go @@ -7,8 +7,8 @@ import ( "context" "net/url" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/cmd/juju/application/store/interface.go b/cmd/juju/application/store/interface.go index 186f7ae2e82..71a2db0ba94 100644 --- a/cmd/juju/application/store/interface.go +++ b/cmd/juju/application/store/interface.go @@ -4,7 +4,7 @@ package store import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" apicharm "github.com/juju/juju/api/client/charms" commoncharm "github.com/juju/juju/api/common/charm" diff --git a/cmd/juju/application/store/mocks/charm_mock.go b/cmd/juju/application/store/mocks/charm_mock.go index 66d96ed4dbf..b70beeecc5e 100644 --- a/cmd/juju/application/store/mocks/charm_mock.go +++ b/cmd/juju/application/store/mocks/charm_mock.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/cmd/juju/application/store/mocks/store_mock.go b/cmd/juju/application/store/mocks/store_mock.go index 1c8ed2b01c5..74f05ef0a07 100644 --- a/cmd/juju/application/store/mocks/store_mock.go +++ b/cmd/juju/application/store/mocks/store_mock.go @@ -14,7 +14,7 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charms "github.com/juju/juju/api/client/charms" charm0 "github.com/juju/juju/api/common/charm" charmhub "github.com/juju/juju/internal/charmhub" diff --git a/cmd/juju/application/store/resolve.go b/cmd/juju/application/store/resolve.go index 6e8c06ca971..caa79eaf3bd 100644 --- a/cmd/juju/application/store/resolve.go +++ b/cmd/juju/application/store/resolve.go @@ -4,8 +4,8 @@ package store import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) // ResolvedBundle decorates a charm.Bundle instance with a type that implements diff --git a/cmd/juju/application/store/store.go b/cmd/juju/application/store/store.go index 0522dc71df0..0e609b75606 100644 --- a/cmd/juju/application/store/store.go +++ b/cmd/juju/application/store/store.go @@ -4,8 +4,8 @@ package store import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/rpc/params" diff --git a/cmd/juju/application/store/store_test.go b/cmd/juju/application/store/store_test.go index b83cbf7158d..b901e46704b 100644 --- a/cmd/juju/application/store/store_test.go +++ b/cmd/juju/application/store/store_test.go @@ -4,8 +4,8 @@ package store_test import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/cmd/juju/application/utils/interface.go b/cmd/juju/application/utils/interface.go index 64dfe6aea8e..2130aed8afa 100644 --- a/cmd/juju/application/utils/interface.go +++ b/cmd/juju/application/utils/interface.go @@ -4,7 +4,7 @@ package utils import ( - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" apicharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/api/common/charms" diff --git a/cmd/juju/application/utils/mocks/charmresource_mock.go b/cmd/juju/application/utils/mocks/charmresource_mock.go index 646e05540f0..5e4e193f987 100644 --- a/cmd/juju/application/utils/mocks/charmresource_mock.go +++ b/cmd/juju/application/utils/mocks/charmresource_mock.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - resource "github.com/juju/charm/v13/resource" + resource "github.com/juju/juju/internal/charm/resource" charm "github.com/juju/juju/api/common/charm" charms "github.com/juju/juju/api/common/charms" gomock "go.uber.org/mock/gomock" diff --git a/cmd/juju/application/utils/origin.go b/cmd/juju/application/utils/origin.go index 2388ee3f4c3..4046f4f02ff 100644 --- a/cmd/juju/application/utils/origin.go +++ b/cmd/juju/application/utils/origin.go @@ -4,8 +4,8 @@ package utils import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/core/arch" diff --git a/cmd/juju/application/utils/utils.go b/cmd/juju/application/utils/utils.go index a8476e16ffc..379a3fd4fe0 100644 --- a/cmd/juju/application/utils/utils.go +++ b/cmd/juju/application/utils/utils.go @@ -9,10 +9,10 @@ import ( "os" "strconv" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/mattn/go-isatty" goyaml "gopkg.in/yaml.v2" diff --git a/cmd/juju/application/utils/utils_test.go b/cmd/juju/application/utils/utils_test.go index 85608516028..c9f6fafb8a4 100644 --- a/cmd/juju/application/utils/utils_test.go +++ b/cmd/juju/application/utils/utils_test.go @@ -4,9 +4,9 @@ package utils_test import ( - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/cmd/juju/charmhub/convert.go b/cmd/juju/charmhub/convert.go index 24f39b2c8a8..48842120c28 100644 --- a/cmd/juju/charmhub/convert.go +++ b/cmd/juju/charmhub/convert.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/arch" corebase "github.com/juju/juju/core/base" diff --git a/cmd/juju/charmhub/data.go b/cmd/juju/charmhub/data.go index 9111424b8f6..45edbd5f8d2 100644 --- a/cmd/juju/charmhub/data.go +++ b/cmd/juju/charmhub/data.go @@ -4,7 +4,7 @@ package charmhub import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" ) const ( diff --git a/cmd/juju/charmhub/download.go b/cmd/juju/charmhub/download.go index 91269a1a555..11f41acb41d 100644 --- a/cmd/juju/charmhub/download.go +++ b/cmd/juju/charmhub/download.go @@ -13,10 +13,10 @@ import ( "strings" "syscall" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/core/arch" diff --git a/cmd/juju/charmhub/info.go b/cmd/juju/charmhub/info.go index f85a14131e2..a759f3e4194 100644 --- a/cmd/juju/charmhub/info.go +++ b/cmd/juju/charmhub/info.go @@ -8,10 +8,10 @@ import ( "fmt" "io" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" corebase "github.com/juju/juju/core/base" diff --git a/cmd/juju/charmhub/infowriter.go b/cmd/juju/charmhub/infowriter.go index de82ce52549..4808a18faee 100644 --- a/cmd/juju/charmhub/infowriter.go +++ b/cmd/juju/charmhub/infowriter.go @@ -9,8 +9,8 @@ import ( "io" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "gopkg.in/yaml.v2" corebase "github.com/juju/juju/core/base" diff --git a/cmd/juju/charmhub/infowriter_test.go b/cmd/juju/charmhub/infowriter_test.go index 91b0c7e32a4..876fb9ebba3 100644 --- a/cmd/juju/charmhub/infowriter_test.go +++ b/cmd/juju/charmhub/infowriter_test.go @@ -6,7 +6,7 @@ package charmhub import ( "bytes" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/commands/bootstrap.go b/cmd/juju/commands/bootstrap.go index 4d5104d89ae..49f0f2dc584 100644 --- a/cmd/juju/commands/bootstrap.go +++ b/cmd/juju/commands/bootstrap.go @@ -12,12 +12,12 @@ import ( "sort" "strings" - "github.com/juju/charm/v13" jujuclock "github.com/juju/clock" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/featureflag" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/naturalsort" "github.com/juju/schema" diff --git a/cmd/juju/commands/helptool.go b/cmd/juju/commands/helptool.go index 20c7c06cc1f..790fb3f2df3 100644 --- a/cmd/juju/commands/helptool.go +++ b/cmd/juju/commands/helptool.go @@ -8,10 +8,10 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/core/network" diff --git a/cmd/juju/crossmodel/find_test.go b/cmd/juju/crossmodel/find_test.go index 75008c4f099..84846262652 100644 --- a/cmd/juju/crossmodel/find_test.go +++ b/cmd/juju/crossmodel/find_test.go @@ -6,10 +6,10 @@ package crossmodel import ( "fmt" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/crossmodel/list.go b/cmd/juju/crossmodel/list.go index bade1b31ae3..a20becf9dff 100644 --- a/cmd/juju/crossmodel/list.go +++ b/cmd/juju/crossmodel/list.go @@ -8,10 +8,10 @@ import ( "regexp" "time" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/api/client/applicationoffers" jujucmd "github.com/juju/juju/cmd" diff --git a/cmd/juju/crossmodel/list_test.go b/cmd/juju/crossmodel/list_test.go index 9fec168b17f..371cba320e8 100644 --- a/cmd/juju/crossmodel/list_test.go +++ b/cmd/juju/crossmodel/list_test.go @@ -7,10 +7,10 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/crossmodel/remoteendpoints.go b/cmd/juju/crossmodel/remoteendpoints.go index c8e5c3adfe6..507220753af 100644 --- a/cmd/juju/crossmodel/remoteendpoints.go +++ b/cmd/juju/crossmodel/remoteendpoints.go @@ -4,7 +4,7 @@ package crossmodel import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/api/client/applicationoffers" "github.com/juju/juju/cmd/modelcmd" diff --git a/cmd/juju/crossmodel/show_test.go b/cmd/juju/crossmodel/show_test.go index bae2b09cc50..e9449c2530f 100644 --- a/cmd/juju/crossmodel/show_test.go +++ b/cmd/juju/crossmodel/show_test.go @@ -6,10 +6,10 @@ package crossmodel import ( "os" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/payload/util_test.go b/cmd/juju/payload/util_test.go index 463d71be3a8..ffcd26d8164 100644 --- a/cmd/juju/payload/util_test.go +++ b/cmd/juju/payload/util_test.go @@ -6,7 +6,7 @@ package payload import ( "fmt" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/payloads" ) diff --git a/cmd/juju/resource/charmresources.go b/cmd/juju/resource/charmresources.go index 846eda51b63..01ab422ba84 100644 --- a/cmd/juju/resource/charmresources.go +++ b/cmd/juju/resource/charmresources.go @@ -4,11 +4,11 @@ package resource import ( - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/api" "github.com/juju/juju/api/client/charms" diff --git a/cmd/juju/resource/charmresources_test.go b/cmd/juju/resource/charmresources_test.go index 36fbc05f414..cf92a6425ee 100644 --- a/cmd/juju/resource/charmresources_test.go +++ b/cmd/juju/resource/charmresources_test.go @@ -6,8 +6,8 @@ package resource_test import ( "strings" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/resource/deploy.go b/cmd/juju/resource/deploy.go index eea93d9c3ac..bf5c817b472 100644 --- a/cmd/juju/resource/deploy.go +++ b/cmd/juju/resource/deploy.go @@ -7,8 +7,8 @@ import ( "context" "io" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" apiresources "github.com/juju/juju/api/client/resources" "github.com/juju/juju/cmd/modelcmd" diff --git a/cmd/juju/resource/deploy_test.go b/cmd/juju/resource/deploy_test.go index e27464b8998..2b667452e6a 100644 --- a/cmd/juju/resource/deploy_test.go +++ b/cmd/juju/resource/deploy_test.go @@ -11,8 +11,8 @@ import ( "path" "strings" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/resource/formatter.go b/cmd/juju/resource/formatter.go index 95bc51ff8be..ce0dacfc987 100644 --- a/cmd/juju/resource/formatter.go +++ b/cmd/juju/resource/formatter.go @@ -7,8 +7,8 @@ import ( "fmt" "sort" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/core/resources" diff --git a/cmd/juju/resource/formatter_test.go b/cmd/juju/resource/formatter_test.go index 871b63f1ed9..ee7fae21be2 100644 --- a/cmd/juju/resource/formatter_test.go +++ b/cmd/juju/resource/formatter_test.go @@ -8,7 +8,7 @@ import ( "strings" "time" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/cmd/juju/resource/list_test.go b/cmd/juju/resource/list_test.go index 89a9dd32e2f..73fa83852f0 100644 --- a/cmd/juju/resource/list_test.go +++ b/cmd/juju/resource/list_test.go @@ -6,8 +6,8 @@ package resource_test import ( "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/cmd/juju/resource/output_tabular_test.go b/cmd/juju/resource/output_tabular_test.go index ff3eea139c6..41242db517b 100644 --- a/cmd/juju/resource/output_tabular_test.go +++ b/cmd/juju/resource/output_tabular_test.go @@ -7,7 +7,7 @@ import ( "bytes" "time" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/resource/resource.go b/cmd/juju/resource/resource.go index c0ea893618a..3318ea27dad 100644 --- a/cmd/juju/resource/resource.go +++ b/cmd/juju/resource/resource.go @@ -7,8 +7,8 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/resource" ) // resourceValue associates a resource name to a value. diff --git a/cmd/juju/resource/stub_test.go b/cmd/juju/resource/stub_test.go index b5a2e81950f..f6abadcf36a 100644 --- a/cmd/juju/resource/stub_test.go +++ b/cmd/juju/resource/stub_test.go @@ -7,8 +7,8 @@ import ( "context" "io" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jujuresource "github.com/juju/juju/cmd/juju/resource" diff --git a/cmd/juju/resource/upload.go b/cmd/juju/resource/upload.go index 049a7f2efd6..eec6b31c746 100644 --- a/cmd/juju/resource/upload.go +++ b/cmd/juju/resource/upload.go @@ -7,9 +7,9 @@ import ( "context" "io" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/cmd/v4" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/api/client/resources" diff --git a/cmd/juju/resource/upload_test.go b/cmd/juju/resource/upload_test.go index 5a9b7d61e93..99e450ef129 100644 --- a/cmd/juju/resource/upload_test.go +++ b/cmd/juju/resource/upload_test.go @@ -6,8 +6,8 @@ package resource_test import ( "bytes" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/cmd/juju/resource/util_test.go b/cmd/juju/resource/util_test.go index 1d43a2f2fad..123574e8ad0 100644 --- a/cmd/juju/resource/util_test.go +++ b/cmd/juju/resource/util_test.go @@ -7,9 +7,9 @@ import ( "bytes" "strings" - charmresource "github.com/juju/charm/v13/resource" jujucmd "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" ) diff --git a/cmd/juju/resource/validation.go b/cmd/juju/resource/validation.go index de90642bc18..44164449df6 100644 --- a/cmd/juju/resource/validation.go +++ b/cmd/juju/resource/validation.go @@ -11,8 +11,8 @@ import ( "net/http" "strings" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "gopkg.in/yaml.v2" "github.com/juju/juju/cmd/juju/application/utils" diff --git a/cmd/juju/ssh/debugcode_test.go b/cmd/juju/ssh/debugcode_test.go index 1841710254d..406aabf84bc 100644 --- a/cmd/juju/ssh/debugcode_test.go +++ b/cmd/juju/ssh/debugcode_test.go @@ -8,8 +8,8 @@ import ( "regexp" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4/cmdtesting" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/cmd/juju/ssh/debughooks.go b/cmd/juju/ssh/debughooks.go index 0e2cc64f7b4..46ab4d3e0f0 100644 --- a/cmd/juju/ssh/debughooks.go +++ b/cmd/juju/ssh/debughooks.go @@ -8,11 +8,11 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/retry" diff --git a/cmd/juju/ssh/debughooks_test.go b/cmd/juju/ssh/debughooks_test.go index 65c07ce5689..a23aa2b45fc 100644 --- a/cmd/juju/ssh/debughooks_test.go +++ b/cmd/juju/ssh/debughooks_test.go @@ -9,10 +9,10 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/retry" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/cmd/juju/ssh/interface.go b/cmd/juju/ssh/interface.go index e47fbb1a2d0..b49a6147549 100644 --- a/cmd/juju/ssh/interface.go +++ b/cmd/juju/ssh/interface.go @@ -4,7 +4,7 @@ package ssh import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/client/application" diff --git a/cmd/juju/ssh/mocks/package_mock.go b/cmd/juju/ssh/mocks/package_mock.go index 5f16607ea71..492e243960d 100644 --- a/cmd/juju/ssh/mocks/package_mock.go +++ b/cmd/juju/ssh/mocks/package_mock.go @@ -15,7 +15,7 @@ import ( reflect "reflect" time "time" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" api "github.com/juju/juju/api" application "github.com/juju/juju/api/client/application" client "github.com/juju/juju/api/client/client" diff --git a/cmd/juju/ssh/ssh_container_test.go b/cmd/juju/ssh/ssh_container_test.go index 80b9d23cf07..c4cf77f0ee6 100644 --- a/cmd/juju/ssh/ssh_container_test.go +++ b/cmd/juju/ssh/ssh_container_test.go @@ -9,8 +9,8 @@ import ( "os" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/cmd/juju/status/formatter.go b/cmd/juju/status/formatter.go index 6611a40242a..b4e32812aa1 100644 --- a/cmd/juju/status/formatter.go +++ b/cmd/juju/status/formatter.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/naturalsort" diff --git a/cmd/juju/status/output_tabular.go b/cmd/juju/status/output_tabular.go index f1ec10877bf..4ea3860ff22 100644 --- a/cmd/juju/status/output_tabular.go +++ b/cmd/juju/status/output_tabular.go @@ -13,9 +13,9 @@ import ( "github.com/docker/distribution/reference" "github.com/juju/ansiterm" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/naturalsort" "github.com/juju/version/v2" diff --git a/cmd/juju/status/status_internal_test.go b/cmd/juju/status/status_internal_test.go index a7cc95633c9..bb5de24edc0 100644 --- a/cmd/juju/status/status_internal_test.go +++ b/cmd/juju/status/status_internal_test.go @@ -14,10 +14,10 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/cmd/jujud-controller/agent/bootstrap_test.go b/cmd/jujud-controller/agent/bootstrap_test.go index 6974bf4f798..c1dd79388cf 100644 --- a/cmd/jujud-controller/agent/bootstrap_test.go +++ b/cmd/jujud-controller/agent/bootstrap_test.go @@ -12,11 +12,11 @@ import ( "path/filepath" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" mgotesting "github.com/juju/mgo/v3/testing" "github.com/juju/names/v5" diff --git a/core/actions/actions.go b/core/actions/actions.go index 5213b39f192..7eee62cf95b 100644 --- a/core/actions/actions.go +++ b/core/actions/actions.go @@ -7,7 +7,7 @@ package actions import ( "strings" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" ) // JujuExecActionName defines the action name used by juju-exec. diff --git a/core/assumes/featureset.go b/core/assumes/featureset.go index 5da8f6b4c9d..9e0d99e5c1e 100644 --- a/core/assumes/featureset.go +++ b/core/assumes/featureset.go @@ -4,8 +4,8 @@ package assumes import ( - chassumes "github.com/juju/charm/v13/assumes" "github.com/juju/collections/set" + chassumes "github.com/juju/juju/internal/charm/assumes" "github.com/juju/version/v2" ) diff --git a/core/assumes/sat_checker.go b/core/assumes/sat_checker.go index a6d6fce1970..458a24bb3ec 100644 --- a/core/assumes/sat_checker.go +++ b/core/assumes/sat_checker.go @@ -4,8 +4,8 @@ package assumes import ( - chassumes "github.com/juju/charm/v13/assumes" "github.com/juju/errors" + chassumes "github.com/juju/juju/internal/charm/assumes" ) // A link to a web page with additional information about features, diff --git a/core/assumes/sat_checker_test.go b/core/assumes/sat_checker_test.go index db6e67beecb..474f162a6ae 100644 --- a/core/assumes/sat_checker_test.go +++ b/core/assumes/sat_checker_test.go @@ -6,7 +6,7 @@ package assumes import ( "strings" - chassumes "github.com/juju/charm/v13/assumes" + chassumes "github.com/juju/juju/internal/charm/assumes" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/core/base/base.go b/core/base/base.go index 11e3636c732..d81068aa0aa 100644 --- a/core/base/base.go +++ b/core/base/base.go @@ -7,9 +7,9 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) // Base represents an OS/Channel. diff --git a/core/base/base_test.go b/core/base/base_test.go index 0517626df77..8febd3584e6 100644 --- a/core/base/base_test.go +++ b/core/base/base_test.go @@ -4,7 +4,7 @@ package base import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/core/charm/adaptor.go b/core/charm/adaptor.go index 5798fd68307..c61646e90d1 100644 --- a/core/charm/adaptor.go +++ b/core/charm/adaptor.go @@ -3,7 +3,7 @@ package charm -import "github.com/juju/charm/v13" +import "github.com/juju/juju/internal/charm" func NewCharmInfoAdaptor(meta EssentialMetadata) charmInfoAdaptor { return charmInfoAdaptor{meta: meta} diff --git a/core/charm/channel.go b/core/charm/channel.go index f03634b8207..95ca837cfe9 100644 --- a/core/charm/channel.go +++ b/core/charm/channel.go @@ -4,7 +4,7 @@ package charm import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" ) const ( diff --git a/core/charm/charm_mock_test.go b/core/charm/charm_mock_test.go index 644c602cfde..68678d65787 100644 --- a/core/charm/charm_mock_test.go +++ b/core/charm/charm_mock_test.go @@ -12,7 +12,7 @@ package charm import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/core/charm/charmpath.go b/core/charm/charmpath.go index fb0ca0cbce4..f34a6715e27 100644 --- a/core/charm/charmpath.go +++ b/core/charm/charmpath.go @@ -9,8 +9,8 @@ import ( "path/filepath" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/base" ) diff --git a/core/charm/charmpath_test.go b/core/charm/charmpath_test.go index 9c355c179c4..aafd43c974b 100644 --- a/core/charm/charmpath_test.go +++ b/core/charm/charmpath_test.go @@ -7,7 +7,7 @@ import ( "os" "path/filepath" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/core/charm/computedbase.go b/core/charm/computedbase.go index 6819690f5a5..b60b46c4d24 100644 --- a/core/charm/computedbase.go +++ b/core/charm/computedbase.go @@ -7,10 +7,10 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/collections/transform" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/base" ) diff --git a/core/charm/computedbase_test.go b/core/charm/computedbase_test.go index 81427244517..6e6e3e27778 100644 --- a/core/charm/computedbase_test.go +++ b/core/charm/computedbase_test.go @@ -4,8 +4,8 @@ package charm import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/core/charm/format.go b/core/charm/format.go index b5f9af203ea..10e02445ea2 100644 --- a/core/charm/format.go +++ b/core/charm/format.go @@ -3,7 +3,7 @@ package charm -import "github.com/juju/charm/v13" +import "github.com/juju/juju/internal/charm" // MetadataFormat of the parsed charm. type MetadataFormat int diff --git a/core/charm/format_test.go b/core/charm/format_test.go index 864dc4d8aee..aea3772f0f7 100644 --- a/core/charm/format_test.go +++ b/core/charm/format_test.go @@ -4,7 +4,7 @@ package charm import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/core/charm/origin.go b/core/charm/origin.go index b8db6f7677a..d1d5dfd0610 100644 --- a/core/charm/origin.go +++ b/core/charm/origin.go @@ -7,8 +7,8 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) // Source represents the source of the charm. diff --git a/core/charm/repository.go b/core/charm/repository.go index b30c07139f9..c4e0b23dee5 100644 --- a/core/charm/repository.go +++ b/core/charm/repository.go @@ -7,8 +7,8 @@ import ( "context" "net/url" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" ) // Repository describes an API for querying charm/bundle information and diff --git a/core/crossmodel/interface.go b/core/crossmodel/interface.go index d82942db9c2..34c5dbe89dd 100644 --- a/core/crossmodel/interface.go +++ b/core/crossmodel/interface.go @@ -4,7 +4,7 @@ package crossmodel import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "gopkg.in/macaroon.v2" "github.com/juju/juju/rpc/params" diff --git a/core/crossmodel/params.go b/core/crossmodel/params.go index a0368081923..7e5cddc31dd 100644 --- a/core/crossmodel/params.go +++ b/core/crossmodel/params.go @@ -6,7 +6,7 @@ package crossmodel import ( "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/permission" "github.com/juju/juju/core/relation" diff --git a/core/model/charm_mock_test.go b/core/model/charm_mock_test.go index e1b0d217219..b40b3c09eb1 100644 --- a/core/model/charm_mock_test.go +++ b/core/model/charm_mock_test.go @@ -12,7 +12,7 @@ package model_test import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/core/payloads/filter_test.go b/core/payloads/filter_test.go index a00fbf5a52f..b49ec30c81a 100644 --- a/core/payloads/filter_test.go +++ b/core/payloads/filter_test.go @@ -4,7 +4,7 @@ package payloads_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/core/payloads/payload.go b/core/payloads/payload.go index 353c328dbcd..5d9a0fb89d1 100644 --- a/core/payloads/payload.go +++ b/core/payloads/payload.go @@ -4,8 +4,8 @@ package payloads import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) // Payload holds information about a charm payload. diff --git a/core/payloads/payload_test.go b/core/payloads/payload_test.go index 89549173450..2d088083017 100644 --- a/core/payloads/payload_test.go +++ b/core/payloads/payload_test.go @@ -4,7 +4,7 @@ package payloads_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/core/resources/application.go b/core/resources/application.go index 85abdbb0825..3706b3d2e3c 100644 --- a/core/resources/application.go +++ b/core/resources/application.go @@ -4,8 +4,8 @@ package resources import ( - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" ) diff --git a/core/resources/application_test.go b/core/resources/application_test.go index e9935834235..52de5f3a891 100644 --- a/core/resources/application_test.go +++ b/core/resources/application_test.go @@ -4,7 +4,7 @@ package resources_test import ( - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/core/resources/content.go b/core/resources/content.go index da66344eb3e..91b93c5803c 100644 --- a/core/resources/content.go +++ b/core/resources/content.go @@ -9,8 +9,8 @@ import ( "io" "os" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/utils/v4" ) diff --git a/core/resources/resource.go b/core/resources/resource.go index 07b5d02e62a..07c411a0e79 100644 --- a/core/resources/resource.go +++ b/core/resources/resource.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/resource" ) // Resource defines a single resource within a Juju model. diff --git a/core/resources/resource_test.go b/core/resources/resource_test.go index c073f98d592..0fe1a707d1c 100644 --- a/core/resources/resource_test.go +++ b/core/resources/resource_test.go @@ -6,8 +6,8 @@ package resources_test import ( "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/core/resources/serialization.go b/core/resources/serialization.go index 85ebdd66656..7b1221b3d29 100644 --- a/core/resources/serialization.go +++ b/core/resources/serialization.go @@ -4,8 +4,8 @@ package resources import ( - "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/resource" ) // DeserializeFingerprint converts the serialized fingerprint back into diff --git a/core/resources/serialization_test.go b/core/resources/serialization_test.go index acb1755ba4e..03a269e7988 100644 --- a/core/resources/serialization_test.go +++ b/core/resources/serialization_test.go @@ -6,8 +6,8 @@ package resources_test import ( "strings" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/core/resources/testing/resource.go b/core/resources/testing/resource.go index 736ca115a68..d7d0d293859 100644 --- a/core/resources/testing/resource.go +++ b/core/resources/testing/resource.go @@ -8,7 +8,7 @@ import ( "strings" "time" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/testing/filetesting" diff --git a/core/resources/util_test.go b/core/resources/util_test.go index 77487fe1d3a..6cfa21ab5b9 100644 --- a/core/resources/util_test.go +++ b/core/resources/util_test.go @@ -6,7 +6,7 @@ package resources_test import ( "strings" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" ) diff --git a/core/settings/settings.go b/core/settings/settings.go index 8d36e770bcc..4ceccb6ea1b 100644 --- a/core/settings/settings.go +++ b/core/settings/settings.go @@ -6,8 +6,8 @@ package settings import ( "fmt" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) const ( diff --git a/domain/application/service/package_mock_test.go b/domain/application/service/package_mock_test.go index a2c3bd7a24f..aa87dfecb14 100644 --- a/domain/application/service/package_mock_test.go +++ b/domain/application/service/package_mock_test.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" application "github.com/juju/juju/domain/application" storage "github.com/juju/juju/domain/storage" gomock "go.uber.org/mock/gomock" diff --git a/domain/application/service/params.go b/domain/application/service/params.go index 0fad9ff26b2..2a6e069d0d5 100644 --- a/domain/application/service/params.go +++ b/domain/application/service/params.go @@ -4,7 +4,7 @@ package service import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" ) diff --git a/domain/application/service/service.go b/domain/application/service/service.go index 056f31b8e52..372b818602a 100644 --- a/domain/application/service/service.go +++ b/domain/application/service/service.go @@ -7,8 +7,8 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/logger" coremodel "github.com/juju/juju/core/model" diff --git a/domain/application/service/service_test.go b/domain/application/service/service_test.go index b8f5996d595..04305ccf728 100644 --- a/domain/application/service/service_test.go +++ b/domain/application/service/service_test.go @@ -6,8 +6,8 @@ package service import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/domain/storage/defaults.go b/domain/storage/defaults.go index b7355195979..0815a420586 100644 --- a/domain/storage/defaults.go +++ b/domain/storage/defaults.go @@ -6,8 +6,8 @@ package storage import ( "fmt" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" coremodel "github.com/juju/juju/core/model" diff --git a/domain/storage/defaults_test.go b/domain/storage/defaults_test.go index 82ab9666a35..724e3a58cb4 100644 --- a/domain/storage/defaults_test.go +++ b/domain/storage/defaults_test.go @@ -4,7 +4,7 @@ package storage_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/domain/storage/validation.go b/domain/storage/validation.go index 421cea5ea6e..175b856881b 100644 --- a/domain/storage/validation.go +++ b/domain/storage/validation.go @@ -7,9 +7,9 @@ import ( "context" "github.com/dustin/go-humanize" - "github.com/juju/charm/v13" "github.com/juju/collections/transform" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" coremodel "github.com/juju/juju/core/model" storageerrors "github.com/juju/juju/domain/storage/errors" diff --git a/domain/storage/validation_test.go b/domain/storage/validation_test.go index f3038ca5900..bad0dc4279b 100644 --- a/domain/storage/validation_test.go +++ b/domain/storage/validation_test.go @@ -7,8 +7,8 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/environs/bootstrap/bootstrap.go b/environs/bootstrap/bootstrap.go index be1b704105b..bd8a0e27562 100644 --- a/environs/bootstrap/bootstrap.go +++ b/environs/bootstrap/bootstrap.go @@ -10,9 +10,9 @@ import ( "os" "path/filepath" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/utils/v4" "github.com/juju/utils/v4/ssh" diff --git a/environs/bootstrap/bootstrap_test.go b/environs/bootstrap/bootstrap_test.go index bd1920415de..310e8115275 100644 --- a/environs/bootstrap/bootstrap_test.go +++ b/environs/bootstrap/bootstrap_test.go @@ -13,9 +13,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/go.mod b/go.mod index 99dec6cb1a0..793e9f6b69a 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,6 @@ require ( github.com/hpcloud/tail v1.0.0 github.com/im7mortal/kmutex v1.0.1 github.com/juju/ansiterm v1.0.0 - github.com/juju/charm/v13 v13.1.0 github.com/juju/clock v1.0.3 github.com/juju/cmd/v4 v4.0.0 github.com/juju/collections v1.0.4 @@ -93,6 +92,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/mittwald/vaultgo v0.1.4 github.com/moby/sys/mountinfo v0.7.1 + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/oracle/oci-go-sdk/v65 v65.55.0 github.com/packethost/packngo v0.28.1 github.com/peterh/liner v1.2.1 @@ -119,6 +119,7 @@ require ( golang.org/x/tools v0.16.1 google.golang.org/api v0.154.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c + gopkg.in/gobwas/glob.v0 v0.2.3 gopkg.in/httprequest.v1 v1.2.1 gopkg.in/ini.v1 v1.67.0 gopkg.in/juju/environschema.v1 v1.0.1 @@ -253,7 +254,6 @@ require ( github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/muhlemmer/gu v0.3.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect @@ -307,7 +307,6 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect - gopkg.in/gobwas/glob.v0 v0.2.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 997ec23072d..06094748de6 100644 --- a/go.sum +++ b/go.sum @@ -479,8 +479,6 @@ github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/ansiterm v1.0.0 h1:gmMvnZRq7JZJx6jkfSq9/+2LMrVEwGwt7UR6G+lmDEg= github.com/juju/ansiterm v1.0.0/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384= -github.com/juju/charm/v13 v13.1.0 h1:HjgiMe5La6NMmL9ilEQYUqzSg3C/WyZRbYDTwlqpSMk= -github.com/juju/charm/v13 v13.1.0/go.mod h1:WzmI80zm8sW84aYfvrmHDt6xJPsoNYj5kbxLm05ezGQ= github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= github.com/juju/clock v0.0.0-20220202072423-1b0f830854c4/go.mod h1:zDZCPSgCJQINeZtQwHx2/cFk4seaBC8Yiqe8V82xiP0= github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a/go.mod h1:GZ/FY8Cqw3KHG6DwRVPUKbSPTAwyrU28xFi5cqZnLsc= diff --git a/internal/bootstrap/bootstrap_mock_test.go b/internal/bootstrap/bootstrap_mock_test.go index b66d8b0922e..ab3609ca786 100644 --- a/internal/bootstrap/bootstrap_mock_test.go +++ b/internal/bootstrap/bootstrap_mock_test.go @@ -15,7 +15,7 @@ import ( http "net/http" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" base "github.com/juju/juju/core/base" charm0 "github.com/juju/juju/core/charm" instance "github.com/juju/juju/core/instance" diff --git a/internal/bootstrap/charm_mock_test.go b/internal/bootstrap/charm_mock_test.go index bfefbe858b2..71d3940ea70 100644 --- a/internal/bootstrap/charm_mock_test.go +++ b/internal/bootstrap/charm_mock_test.go @@ -14,8 +14,8 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/charm/v13" - resource "github.com/juju/charm/v13/resource" + charm "github.com/juju/juju/internal/charm" + resource "github.com/juju/juju/internal/charm/resource" charm0 "github.com/juju/juju/core/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/internal/bootstrap/deployer.go b/internal/bootstrap/deployer.go index 146ab1194df..e9bea040abf 100644 --- a/internal/bootstrap/deployer.go +++ b/internal/bootstrap/deployer.go @@ -9,8 +9,8 @@ import ( "os" "path/filepath" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/schema" "gopkg.in/juju/environschema.v1" diff --git a/internal/bootstrap/deployer_test.go b/internal/bootstrap/deployer_test.go index 7930ebd6887..98d21c7fb70 100644 --- a/internal/bootstrap/deployer_test.go +++ b/internal/bootstrap/deployer_test.go @@ -11,8 +11,8 @@ import ( "path/filepath" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/schema" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/bootstrap/downloader_mock_test.go b/internal/bootstrap/downloader_mock_test.go index 938cfea52d9..6af4ca65f24 100644 --- a/internal/bootstrap/downloader_mock_test.go +++ b/internal/bootstrap/downloader_mock_test.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charm0 "github.com/juju/juju/core/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/internal/bootstrap/package_test.go b/internal/bootstrap/package_test.go index b19fab70818..89ad5193b71 100644 --- a/internal/bootstrap/package_test.go +++ b/internal/bootstrap/package_test.go @@ -6,7 +6,7 @@ package bootstrap import ( "testing" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/internal/bundle/changes/changes.go b/internal/bundle/changes/changes.go index 5b82a39be26..b9c90e9b939 100644 --- a/internal/bundle/changes/changes.go +++ b/internal/bundle/changes/changes.go @@ -10,9 +10,9 @@ import ( "sort" "strings" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" corebase "github.com/juju/juju/core/base" "github.com/juju/juju/core/logger" diff --git a/internal/bundle/changes/changes_test.go b/internal/bundle/changes/changes_test.go index 33bf9dbd87d..0aa6c4c6fe3 100644 --- a/internal/bundle/changes/changes_test.go +++ b/internal/bundle/changes/changes_test.go @@ -12,8 +12,8 @@ import ( "sort" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/bundle/changes/diff.go b/internal/bundle/changes/diff.go index 93a52fb151c..f374046527f 100644 --- a/internal/bundle/changes/diff.go +++ b/internal/bundle/changes/diff.go @@ -8,9 +8,9 @@ import ( "sort" "strings" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/logger" ) diff --git a/internal/bundle/changes/diff_test.go b/internal/bundle/changes/diff_test.go index 9ffdbf37271..961fb8c6bd5 100644 --- a/internal/bundle/changes/diff_test.go +++ b/internal/bundle/changes/diff_test.go @@ -6,7 +6,7 @@ package bundlechanges_test import ( "strings" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" diff --git a/internal/bundle/changes/handlers.go b/internal/bundle/changes/handlers.go index 0f8d48aba97..3ed6f12a35f 100644 --- a/internal/bundle/changes/handlers.go +++ b/internal/bundle/changes/handlers.go @@ -8,9 +8,9 @@ import ( "sort" "strings" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/naturalsort" corebase "github.com/juju/juju/core/base" diff --git a/internal/bundle/changes/handlers_test.go b/internal/bundle/changes/handlers_test.go index b0d2e9e6aae..85616ec1824 100644 --- a/internal/bundle/changes/handlers_test.go +++ b/internal/bundle/changes/handlers_test.go @@ -4,7 +4,7 @@ package bundlechanges import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/bundle/changes/model.go b/internal/bundle/changes/model.go index f0014185bfa..871bf94d041 100644 --- a/internal/bundle/changes/model.go +++ b/internal/bundle/changes/model.go @@ -8,8 +8,8 @@ import ( "sort" "strconv" - "github.com/juju/charm/v13" "github.com/juju/collections/set" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/naturalsort" "github.com/kr/pretty" diff --git a/internal/bundle/changes/model_test.go b/internal/bundle/changes/model_test.go index f5ded399b2c..6ea93d6ca03 100644 --- a/internal/bundle/changes/model_test.go +++ b/internal/bundle/changes/model_test.go @@ -6,7 +6,7 @@ package bundlechanges import ( "bytes" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/naturalsort" "github.com/juju/testing" diff --git a/internal/charm/actions.go b/internal/charm/actions.go new file mode 100644 index 00000000000..6a000b33f3d --- /dev/null +++ b/internal/charm/actions.go @@ -0,0 +1,308 @@ +// Copyright 2011-2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "io" + "io/ioutil" + "regexp" + "strings" + + "github.com/juju/errors" + gjs "github.com/juju/gojsonschema" + "gopkg.in/yaml.v2" +) + +var prohibitedSchemaKeys = map[string]bool{"$ref": true, "$schema": true} + +var actionNameRule = regexp.MustCompile("^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$") + +// Export `actionNameRule` variable to different contexts. +func GetActionNameRule() *regexp.Regexp { + return actionNameRule +} + +// Actions defines the available actions for the charm. Additional params +// may be added as metadata at a future time (e.g. version.) +type Actions struct { + ActionSpecs map[string]ActionSpec `yaml:"actions,omitempty" bson:",omitempty"` +} + +// Build this out further if it becomes necessary. +func NewActions() *Actions { + return &Actions{} +} + +// ActionSpec is a definition of the parameters and traits of an Action. +// The Params map is expected to conform to JSON-Schema Draft 4 as defined at +// http://json-schema.org/draft-04/schema# (see http://json-schema.org/latest/json-schema-core.html) +type ActionSpec struct { + Description string + Parallel bool + ExecutionGroup string + Params map[string]interface{} +} + +// ValidateParams validates the passed params map against the given ActionSpec +// and returns any error encountered. +// Usage: +// err := ch.Actions().ActionSpecs["snapshot"].ValidateParams(someMap) +func (spec *ActionSpec) ValidateParams(params map[string]interface{}) error { + // Load the schema from the Charm. + specLoader := gjs.NewGoLoader(spec.Params) + schema, err := gjs.NewSchema(specLoader) + if err != nil { + return err + } + + // Load the params as a document to validate. + // If an empty map was passed, we need an empty map to validate against. + p := map[string]interface{}{} + if len(params) > 0 { + p = params + } + docLoader := gjs.NewGoLoader(p) + results, err := schema.Validate(docLoader) + if err != nil { + return err + } + if results.Valid() { + return nil + } + + // Handle any errors generated by the Validate(). + var errorStrings []string + for _, validationError := range results.Errors() { + errorStrings = append(errorStrings, validationError.String()) + } + return errors.Errorf("validation failed: %s", strings.Join(errorStrings, "; ")) +} + +// InsertDefaults inserts the schema's default values in target using +// github.com/juju/gojsonschema. If a nil target is received, an empty map +// will be created as the target. The target is then mutated to include the +// defaults. +// +// The returned map will be the transformed or created target map. +func (spec *ActionSpec) InsertDefaults(target map[string]interface{}) (map[string]interface{}, error) { + specLoader := gjs.NewGoLoader(spec.Params) + schema, err := gjs.NewSchema(specLoader) + if err != nil { + return target, err + } + + return schema.InsertDefaults(target) +} + +// ReadActionsYaml builds an Actions spec from a charm's actions.yaml. +func ReadActionsYaml(charmName string, r io.Reader) (*Actions, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + result := &Actions{ + ActionSpecs: map[string]ActionSpec{}, + } + + var unmarshaledActions map[string]map[string]interface{} + if err := yaml.Unmarshal(data, &unmarshaledActions); err != nil { + return nil, err + } + + for name, actionSpec := range unmarshaledActions { + if valid := actionNameRule.MatchString(name); !valid { + return nil, fmt.Errorf("bad action name %s", name) + } + if reserved, reason := reservedName(charmName, name); reserved { + return nil, fmt.Errorf( + "cannot use action name %s: %s", + name, reason, + ) + } + + desc := "No description" + parallel := false + executionGroup := "" + thisActionSchema := map[string]interface{}{ + "description": desc, + "type": "object", + "title": name, + "properties": map[string]interface{}{}, + "additionalProperties": false, + } + + for key, value := range actionSpec { + switch key { + case "description": + // These fields must be strings. + typed, ok := value.(string) + if !ok { + return nil, errors.Errorf("value for schema key %q must be a string", key) + } + thisActionSchema[key] = typed + desc = typed + case "title": + // These fields must be strings. + typed, ok := value.(string) + if !ok { + return nil, errors.Errorf("value for schema key %q must be a string", key) + } + thisActionSchema[key] = typed + case "required": + typed, ok := value.([]interface{}) + if !ok { + return nil, errors.Errorf("value for schema key %q must be a YAML list", key) + } + thisActionSchema[key] = typed + case "parallel": + typed, ok := value.(bool) + if !ok { + return nil, errors.Errorf("value for schema key %q must be a bool", key) + } + parallel = typed + case "execution-group": + typed, ok := value.(string) + if !ok { + return nil, errors.Errorf("value for schema key %q must be a string", key) + } + executionGroup = typed + case "params": + // Clean any map[interface{}]interface{}s out so they don't + // cause problems with BSON serialization later. + cleansedParams, err := cleanse(value) + if err != nil { + return nil, err + } + + // JSON-Schema must be a map + typed, ok := cleansedParams.(map[string]interface{}) + if !ok { + return nil, errors.New("params failed to parse as a map") + } + thisActionSchema["properties"] = typed + default: + // In case this has nested maps, we must clean them out. + typed, err := cleanse(value) + if err != nil { + return nil, err + } + thisActionSchema[key] = typed + } + } + + // Make sure the new Params doc conforms to JSON-Schema + // Draft 4 (http://json-schema.org/latest/json-schema-core.html) + schemaLoader := gjs.NewGoLoader(thisActionSchema) + _, err := gjs.NewSchema(schemaLoader) + if err != nil { + return nil, errors.Annotatef(err, "invalid params schema for action schema %s", name) + } + + // Now assign the resulting schema to the final entry for the result. + result.ActionSpecs[name] = ActionSpec{ + Description: desc, + Parallel: parallel, + ExecutionGroup: executionGroup, + Params: thisActionSchema, + } + } + return result, nil +} + +// cleanse rejects schemas containing references or maps keyed with non- +// strings, and coerces acceptable maps to contain only maps with string keys. +func cleanse(input interface{}) (interface{}, error) { + switch typedInput := input.(type) { + // In this case, recurse in. + case map[string]interface{}: + newMap := make(map[string]interface{}) + for key, value := range typedInput { + + if prohibitedSchemaKeys[key] { + return nil, fmt.Errorf("schema key %q not compatible with this version of juju", key) + } + + newValue, err := cleanse(value) + if err != nil { + return nil, err + } + newMap[key] = newValue + } + return newMap, nil + + // Coerce keys to strings and error out if there's a problem; then recurse. + case map[interface{}]interface{}: + newMap := make(map[string]interface{}) + for key, value := range typedInput { + typedKey, ok := key.(string) + if !ok { + return nil, errors.New("map keyed with non-string value") + } + newMap[typedKey] = value + } + return cleanse(newMap) + + // Recurse + case []interface{}: + newSlice := make([]interface{}, 0) + for _, sliceValue := range typedInput { + newSliceValue, err := cleanse(sliceValue) + if err != nil { + return nil, errors.New("map keyed with non-string value") + } + newSlice = append(newSlice, newSliceValue) + } + return newSlice, nil + + // Other kinds of values are OK. + default: + return input, nil + } +} + +// recurseMapOnKeys returns the value of a map keyed recursively by the +// strings given in "keys". Thus, recurseMapOnKeys({a,b}, {a:{b:{c:d}}}) +// would return {c:d}. +func recurseMapOnKeys(keys []string, params map[string]interface{}) (interface{}, bool) { + key, rest := keys[0], keys[1:] + answer, ok := params[key] + + // If we're out of keys, we have our answer. + if len(rest) == 0 { + return answer, ok + } + + // If we're not out of keys, but we tried a key that wasn't in the + // map, there's no answer. + if !ok { + return nil, false + } + + switch typed := answer.(type) { + // If our value is a map[s]i{}, we can keep recursing. + case map[string]interface{}: + return recurseMapOnKeys(keys[1:], typed) + // If it's a map[i{}]i{}, we need to check whether it's a map[s]i{}. + case map[interface{}]interface{}: + m := make(map[string]interface{}) + for k, v := range typed { + if tK, ok := k.(string); ok { + m[tK] = v + } else { + // If it's not, we don't have something we + // can work with. + return nil, false + } + } + // If it is, recurse into it. + return recurseMapOnKeys(keys[1:], m) + + // Otherwise, we're trying to recurse into something we don't know + // how to deal with, so our answer is that we don't have an answer. + default: + return nil, false + } +} diff --git a/internal/charm/actions_test.go b/internal/charm/actions_test.go new file mode 100644 index 00000000000..7fcf13bfef0 --- /dev/null +++ b/internal/charm/actions_test.go @@ -0,0 +1,1097 @@ +// Copyright 2011-2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "bytes" + "encoding/json" + + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" +) + +type ActionsSuite struct{} + +var _ = gc.Suite(&ActionsSuite{}) + +func (s *ActionsSuite) TestNewActions(c *gc.C) { + emptyAction := NewActions() + c.Assert(emptyAction, jc.DeepEquals, &Actions{}) +} + +func (s *ActionsSuite) TestValidateOk(c *gc.C) { + for i, test := range []struct { + description string + actionSpec *ActionSpec + objectToValidate map[string]interface{} + }{{ + description: "Validation of an empty object is ok.", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": false}}, + objectToValidate: nil, + }, { + description: "Validation of one required value.", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}, + objectToValidate: map[string]interface{}{ + "outfile": "out-2014-06-12.bz2", + }, + }, { + description: "Validation of one required and one optional value.", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}, + "quality": map[string]interface{}{ + "description": "Compression quality", + "type": "integer", + "minimum": 0, + "maximum": 9}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}, + objectToValidate: map[string]interface{}{ + "outfile": "out-2014-06-12.bz2", + }, + }, { + description: "Validation of an optional, range limited value.", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}, + "quality": map[string]interface{}{ + "description": "Compression quality", + "type": "integer", + "minimum": 0, + "maximum": 9}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}, + objectToValidate: map[string]interface{}{ + "outfile": "out-2014-06-12.bz2", + "quality": 5, + }, + }, { + description: "Validation of extra params with additionalProperties set to true", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": true}}, + objectToValidate: map[string]interface{}{ + "outfile": "out-2014-06-12.bz2", + "extraParam1": 1, + "extraParam2": 2, + }, + }} { + c.Logf("test %d: %s", i, test.description) + err := test.actionSpec.ValidateParams(test.objectToValidate) + c.Assert(err, jc.ErrorIsNil) + } +} + +func (s *ActionsSuite) TestValidateFail(c *gc.C) { + var validActionTests = []struct { + description string + actionSpec *ActionSpec + badActionJson string + expectedError string + }{{ + description: "Validation of one required value.", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}, + badActionJson: `{"outfile": 5}`, + expectedError: "validation failed: (root).outfile : must be of type string, given 5", + }, { + description: "Restrict to only one property", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "required": []interface{}{"outfile"}, + "additionalProperties": false}}, + badActionJson: `{"outfile": "foo.bz", "bar": "foo"}`, + expectedError: "validation failed: (root) : additional property \"bar\" is not allowed, given {\"bar\":\"foo\",\"outfile\":\"foo.bz\"}", + }, { + description: "Validation of one required and one optional value.", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}, + "quality": map[string]interface{}{ + "description": "Compression quality", + "type": "integer", + "minimum": 0, + "maximum": 9}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}, + badActionJson: `{"quality": 5}`, + expectedError: "validation failed: (root) : \"outfile\" property is missing and required, given {\"quality\":5}", + }, { + description: "Validation of an optional, range limited value.", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}, + "quality": map[string]interface{}{ + "description": "Compression quality", + "type": "integer", + "minimum": 0, + "maximum": 9}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}, + badActionJson: ` +{ "outfile": "out-2014-06-12.bz2", "quality": "two" }`, + expectedError: "validation failed: (root).quality : must be of type integer, given \"two\"", + }, { + description: "Validation of misspelled value.", + actionSpec: &ActionSpec{ + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": false, + }}, + badActionJson: `{"oufile": 5}`, + expectedError: `validation failed: (root) : additional property "oufile" is not allowed, given {"oufile":5}`, + }} + + for i, test := range validActionTests { + c.Logf("test %d: %s", i, test.description) + var params map[string]interface{} + jsonBytes := []byte(test.badActionJson) + err := json.Unmarshal(jsonBytes, ¶ms) + c.Assert(err, gc.IsNil) + err = test.actionSpec.ValidateParams(params) + c.Assert(err.Error(), gc.Equals, test.expectedError) + } +} + +func (s *ActionsSuite) TestCleanseOk(c *gc.C) { + + var goodInterfaceTests = []struct { + description string + acceptableInterface map[string]interface{} + expectedInterface map[string]interface{} + }{{ + description: "An interface requiring no changes.", + acceptableInterface: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": map[string]interface{}{ + "foo1": "val1", + "foo2": "val2"}}, + expectedInterface: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": map[string]interface{}{ + "foo1": "val1", + "foo2": "val2"}}, + }, { + description: "Substitute a single inner map[i]i.", + acceptableInterface: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": map[interface{}]interface{}{ + "foo1": "val1", + "foo2": "val2"}}, + expectedInterface: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": map[string]interface{}{ + "foo1": "val1", + "foo2": "val2"}}, + }, { + description: "Substitute nested inner map[i]i.", + acceptableInterface: map[string]interface{}{ + "key1a": "val1a", + "key2a": "val2a", + "key3a": map[interface{}]interface{}{ + "key1b": "val1b", + "key2b": map[interface{}]interface{}{ + "key1c": "val1c"}}}, + expectedInterface: map[string]interface{}{ + "key1a": "val1a", + "key2a": "val2a", + "key3a": map[string]interface{}{ + "key1b": "val1b", + "key2b": map[string]interface{}{ + "key1c": "val1c"}}}, + }, { + description: "Substitute nested map[i]i within []i.", + acceptableInterface: map[string]interface{}{ + "key1a": "val1a", + "key2a": []interface{}{5, "foo", map[string]interface{}{ + "key1b": "val1b", + "key2b": map[interface{}]interface{}{ + "key1c": "val1c"}}}}, + expectedInterface: map[string]interface{}{ + "key1a": "val1a", + "key2a": []interface{}{5, "foo", map[string]interface{}{ + "key1b": "val1b", + "key2b": map[string]interface{}{ + "key1c": "val1c"}}}}, + }} + + for i, test := range goodInterfaceTests { + c.Logf("test %d: %s", i, test.description) + cleansedInterfaceMap, err := cleanse(test.acceptableInterface) + c.Assert(err, gc.IsNil) + c.Assert(cleansedInterfaceMap, jc.DeepEquals, test.expectedInterface) + } +} + +func (s *ActionsSuite) TestCleanseFail(c *gc.C) { + + var badInterfaceTests = []struct { + description string + failInterface map[string]interface{} + expectedError string + }{{ + description: "An inner map[interface{}]interface{} with an int key.", + failInterface: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": map[interface{}]interface{}{ + "foo1": "val1", + 5: "val2"}}, + expectedError: "map keyed with non-string value", + }, { + description: "An inner []interface{} containing a map[i]i with an int key.", + failInterface: map[string]interface{}{ + "key1a": "val1b", + "key2a": "val2b", + "key3a": []interface{}{"foo1", 5, map[interface{}]interface{}{ + "key1b": "val1b", + "key2b": map[interface{}]interface{}{ + "key1c": "val1c", + 5: "val2c"}}}}, + expectedError: "map keyed with non-string value", + }} + + for i, test := range badInterfaceTests { + c.Logf("test %d: %s", i, test.description) + _, err := cleanse(test.failInterface) + c.Assert(err, gc.NotNil) + c.Assert(err.Error(), gc.Equals, test.expectedError) + } +} + +func (s *ActionsSuite) TestGetActionNameRule(c *gc.C) { + + var regExCheck = []struct { + description string + regExString string + }{{ + description: "Check returned actionNameRule regex", + regExString: "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$", + }} + + for i, t := range regExCheck { + c.Logf("test %d: %v: %#v\n", i, t.description, t.regExString) + obtained := GetActionNameRule() + c.Assert(obtained.String(), gc.Equals, t.regExString) + } +} + +func (s *ActionsSuite) TestReadGoodActionsYaml(c *gc.C) { + var goodActionsYamlTests = []struct { + description string + yaml string + expectedActions *Actions + }{{ + description: "A simple snapshot actions YAML with one parameter.", + yaml: ` +snapshot: + description: Take a snapshot of the database. + params: + outfile: + description: "The file to write out to." + type: string + required: ["outfile"] +`, + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot": { + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}}}, + }, { + description: "An empty Actions definition.", + yaml: "", + expectedActions: &Actions{ + ActionSpecs: map[string]ActionSpec{}, + }, + }, { + description: "A more complex schema with hyphenated names and multiple parameters.", + yaml: ` +snapshot: + description: "Take a snapshot of the database." + params: + outfile: + description: "The file to write out to." + type: "string" + compression-quality: + description: "The compression quality." + type: "integer" + minimum: 0 + maximum: 9 + exclusiveMaximum: false +remote-sync: + description: "Sync a file to a remote host." + params: + file: + description: "The file to send out." + type: "string" + format: "uri" + remote-uri: + description: "The host to sync to." + type: "string" + format: "uri" + util: + description: "The util to perform the sync (rsync or scp.)" + type: "string" + enum: ["rsync", "scp"] + required: ["file", "remote-uri"] +`, + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot": { + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}, + "compression-quality": map[string]interface{}{ + "description": "The compression quality.", + "type": "integer", + "minimum": 0, + "maximum": 9, + "exclusiveMaximum": false}}, + "additionalProperties": false}}, + "remote-sync": { + Description: "Sync a file to a remote host.", + Params: map[string]interface{}{ + "title": "remote-sync", + "description": "Sync a file to a remote host.", + "type": "object", + "properties": map[string]interface{}{ + "file": map[string]interface{}{ + "description": "The file to send out.", + "type": "string", + "format": "uri"}, + "remote-uri": map[string]interface{}{ + "description": "The host to sync to.", + "type": "string", + "format": "uri"}, + "util": map[string]interface{}{ + "description": "The util to perform the sync (rsync or scp.)", + "type": "string", + "enum": []interface{}{"rsync", "scp"}}}, + "additionalProperties": false, + "required": []interface{}{"file", "remote-uri"}}}}}, + }, { + description: "A schema with other keys, e.g. \"definitions\"", + yaml: ` +snapshot: + description: "Take a snapshot of the database." + params: + outfile: + description: "The file to write out to." + type: "string" + compression-quality: + description: "The compression quality." + type: "integer" + minimum: 0 + maximum: 9 + exclusiveMaximum: false + definitions: + diskdevice: {} + something-else: {} +`, + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot": { + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string", + }, + "compression-quality": map[string]interface{}{ + "description": "The compression quality.", + "type": "integer", + "minimum": 0, + "maximum": 9, + "exclusiveMaximum": false, + }, + }, + "additionalProperties": false, + "definitions": map[string]interface{}{ + "diskdevice": map[string]interface{}{}, + "something-else": map[string]interface{}{}, + }, + }, + }, + }}, + }, { + description: "A schema with no \"params\" key, implying no options.", + yaml: ` +snapshot: + description: Take a snapshot of the database. +`, + + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot": { + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "description": "Take a snapshot of the database.", + "title": "snapshot", + "type": "object", + "properties": map[string]interface{}{}, + "additionalProperties": false, + }}}}, + }, { + description: "A schema with no values at all, implying no options.", + yaml: ` +snapshot: +`, + + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot": { + Description: "No description", + Params: map[string]interface{}{ + "description": "No description", + "title": "snapshot", + "type": "object", + "properties": map[string]interface{}{}, + "additionalProperties": false, + }}}}, + }, { + description: "A simple snapshot actions YAML with names ending characters.", + yaml: ` +snapshot-01: + description: Take database first snapshot. + params: + outfile-01: + description: "The file to write out to." + type: string + required: ["outfile"] +`, + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot-01": { + Description: "Take database first snapshot.", + Params: map[string]interface{}{ + "title": "snapshot-01", + "description": "Take database first snapshot.", + "type": "object", + "properties": map[string]interface{}{ + "outfile-01": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}}}, + }, { + description: "A simple snapshot actions YAML with names containing characters.", + yaml: ` +snapshot-0-foo: + description: Take database first snapshot. + params: + outfile: + description: "The file to write out to." + type: string + required: ["outfile"] +`, + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot-0-foo": { + Description: "Take database first snapshot.", + Params: map[string]interface{}{ + "title": "snapshot-0-foo", + "description": "Take database first snapshot.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}}}, + }, { + description: "A simple snapshot actions YAML with names starting characters.", + yaml: ` +01-snapshot: + description: Take database first snapshot. + params: + 01-outfile: + description: "The file to write out to." + type: string + required: ["outfile"] +`, + expectedActions: &Actions{map[string]ActionSpec{ + "01-snapshot": { + Description: "Take database first snapshot.", + Params: map[string]interface{}{ + "title": "01-snapshot", + "description": "Take database first snapshot.", + "type": "object", + "properties": map[string]interface{}{ + "01-outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "additionalProperties": false, + "required": []interface{}{"outfile"}}}}}, + }, { + description: "An action with parallel and execution group values set", + yaml: ` +snapshot: + description: "Take a snapshot of the database." + parallel: true + execution-group: "exec group" +`, + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot": { + Description: "Take a snapshot of the database.", + Parallel: true, + ExecutionGroup: "exec group", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{}, + "additionalProperties": false}, + }, + }}, + }, { + description: "An action with additional properties set to true", + yaml: ` +snapshot: + description: "Take a snapshot of the database." + additionalProperties: true +`, + expectedActions: &Actions{map[string]ActionSpec{ + "snapshot": { + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{}, + "additionalProperties": true, + }, + }, + }}, + }} + + // Beginning of testing loop + for i, test := range goodActionsYamlTests { + c.Logf("test %d: %s", i, test.description) + reader := bytes.NewReader([]byte(test.yaml)) + loadedAction, err := ReadActionsYaml("somecharm", reader) + c.Assert(err, gc.IsNil) + c.Check(loadedAction, jc.DeepEquals, test.expectedActions) + } +} + +func (s *ActionsSuite) TestJujuCharmActionsYaml(c *gc.C) { + actionsYaml := ` +juju-snapshot: + description: Take a snapshot of the database. + params: + outfile: + description: "The file to write out to." + type: string + required: ["outfile"] +` + expectedActions := &Actions{map[string]ActionSpec{ + "juju-snapshot": { + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "title": "juju-snapshot", + "description": "Take a snapshot of the database.", + "type": "object", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string"}}, + "required": []interface{}{"outfile"}, + "additionalProperties": false}}}} + + reader := bytes.NewReader([]byte(actionsYaml)) + loadedAction, err := ReadActionsYaml("juju-charm", reader) + c.Assert(err, gc.IsNil) + c.Check(loadedAction, jc.DeepEquals, expectedActions) +} + +func (s *ActionsSuite) TestReadBadActionsYaml(c *gc.C) { + + var badActionsYamlTests = []struct { + description string + yaml string + expectedError string + }{{ + description: "Reject JSON-Schema containing references.", + yaml: ` +snapshot: + description: Take a snapshot of the database. + params: + $schema: "http://json-schema.org/draft-03/schema#" +`, + expectedError: `schema key "\$schema" not compatible with this version of juju`, + }, { + description: "Reject JSON-Schema containing references.", + yaml: ` +snapshot: + description: Take a snapshot of the database. + params: + outfile: { $ref: "http://json-schema.org/draft-03/schema#" } +`, + expectedError: `schema key "\$ref" not compatible with this version of juju`, + }, { + description: "Malformed YAML: missing key in \"outfile\".", + yaml: ` +snapshot: + description: Take a snapshot of the database. + params: + outfile: + The file to write out to. + type: string + default: foo.bz2 +`, + + expectedError: `yaml: line [0-9]: mapping values are not allowed in this context`, + }, { + description: "Malformed JSON-Schema: $schema element misplaced.", + yaml: ` +snapshot: +description: Take a snapshot of the database. + params: + outfile: + $schema: http://json-schema.org/draft-03/schema# + description: The file to write out to. + type: string + default: foo.bz2 +`, + + expectedError: `yaml: line [0-9]: mapping values are not allowed in this context`, + }, { + description: "Malformed Actions: hyphen at beginning of action name.", + yaml: ` +-snapshot: + description: Take a snapshot of the database. +`, + + expectedError: `bad action name -snapshot`, + }, { + description: "Malformed Actions: hyphen after action name.", + yaml: ` +snapshot-: + description: Take a snapshot of the database. +`, + + expectedError: `bad action name snapshot-`, + }, { + description: "Malformed Actions: caps in action name.", + yaml: ` +Snapshot: + description: Take a snapshot of the database. +`, + + expectedError: `bad action name Snapshot`, + }, { + description: `Reserved Action Name: "juju".`, + yaml: ` +juju: + description: A reserved action. +`, + expectedError: `cannot use action name juju: "juju" is a reserved name`, + }, { + description: `Reserved Action Name: "juju-run".`, + yaml: ` +juju-run: + description: A reserved action. +`, + expectedError: `cannot use action name juju-run: the "juju-" prefix is reserved`, + }, { + description: "A non-string description fails to parse", + yaml: ` +snapshot: + description: ["Take a snapshot of the database."] +`, + expectedError: `value for schema key "description" must be a string`, + }, { + description: "A non-string execution-group fails to parse", + yaml: ` +snapshot: + execution-group: ["Exec group"] +`, + expectedError: `value for schema key "execution-group" must be a string`, + }, { + description: "A non-bool parallel value fails to parse", + yaml: ` +snapshot: + parallel: "not a bool" +`, + expectedError: `value for schema key "parallel" must be a bool`, + }, { + description: "A non-list \"required\" key", + yaml: ` +snapshot: + description: Take a snapshot of the database. + params: + outfile: + description: "The file to write out to." + type: string + required: "outfile" +`, + expectedError: `value for schema key "required" must be a YAML list`, + }, { + description: "A schema with an empty \"params\" key fails to parse", + yaml: ` +snapshot: + description: Take a snapshot of the database. + params: +`, + expectedError: `params failed to parse as a map`, + }, { + description: "A schema with a non-map \"params\" value fails to parse", + yaml: ` +snapshot: + description: Take a snapshot of the database. + params: ["a", "b"] +`, + expectedError: `params failed to parse as a map`, + }, { + description: "\"definitions\" goes against JSON-Schema definition", + yaml: ` +snapshot: + description: "Take a snapshot of the database." + params: + outfile: + description: "The file to write out to." + type: "string" + definitions: + diskdevice: ["a"] + something-else: {"a": "b"} +`, + expectedError: `invalid params schema for action schema snapshot: definitions must be of type array of schemas`, + }, { + description: "excess keys not in the JSON-Schema spec will be rejected", + yaml: ` +snapshot: + description: "Take a snapshot of the database." + params: + outfile: + description: "The file to write out to." + type: "string" + compression-quality: + description: "The compression quality." + type: "integer" + minimum: 0 + maximum: 9 + exclusiveMaximum: false + definitions: + diskdevice: {} + something-else: {} + other-key: ["some", "values"], +`, + expectedError: `yaml: line [0-9]+: did not find expected key`, + }} + + for i, test := range badActionsYamlTests { + c.Logf("test %d: %s", i, test.description) + reader := bytes.NewReader([]byte(test.yaml)) + _, err := ReadActionsYaml("somecharm", reader) + c.Check(err, gc.ErrorMatches, test.expectedError) + } +} + +func (s *ActionsSuite) TestRecurseMapOnKeys(c *gc.C) { + tests := []struct { + should string + givenKeys []string + givenMap map[string]interface{} + expected interface{} + shouldFail bool + }{{ + should: "fail if the specified key was not in the map", + givenKeys: []string{"key", "key2"}, + givenMap: map[string]interface{}{ + "key": map[string]interface{}{ + "key": "value", + }, + }, + shouldFail: true, + }, { + should: "fail if a key was not a string", + givenKeys: []string{"key", "key2"}, + givenMap: map[string]interface{}{ + "key": map[interface{}]interface{}{ + 5: "value", + }, + }, + shouldFail: true, + }, { + should: "fail if we have more keys but not a recursable val", + givenKeys: []string{"key", "key2"}, + givenMap: map[string]interface{}{ + "key": []string{"a", "b", "c"}, + }, + shouldFail: true, + }, { + should: "retrieve a good value", + givenKeys: []string{"key", "key2"}, + givenMap: map[string]interface{}{ + "key": map[string]interface{}{ + "key2": "value", + }, + }, + expected: "value", + }, { + should: "retrieve a map", + givenKeys: []string{"key"}, + givenMap: map[string]interface{}{ + "key": map[string]interface{}{ + "key": "value", + }, + }, + expected: map[string]interface{}{ + "key": "value", + }, + }, { + should: "retrieve a slice", + givenKeys: []string{"key"}, + givenMap: map[string]interface{}{ + "key": []string{"a", "b", "c"}, + }, + expected: []string{"a", "b", "c"}, + }} + + for i, t := range tests { + c.Logf("test %d: should %s\n map: %#v\n keys: %#v", i, t.should, t.givenMap, t.givenKeys) + obtained, failed := recurseMapOnKeys(t.givenKeys, t.givenMap) + c.Assert(!failed, gc.Equals, t.shouldFail) + if !t.shouldFail { + c.Check(obtained, jc.DeepEquals, t.expected) + } + } +} + +func (s *ActionsSuite) TestInsertDefaultValues(c *gc.C) { + schemas := map[string]string{ + "simple": ` +act: + params: + val: + type: string + default: somestr +`[1:], + "complicated": ` +act: + params: + val: + type: object + properties: + foo: + type: string + bar: + type: object + properties: + baz: + type: string + default: boz +`[1:], + "default-object": ` +act: + params: + val: + type: object + default: + foo: bar + bar: + baz: woz +`[1:], + "none": ` +act: + params: + val: + type: object + properties: + var: + type: object + properties: + x: + type: string +`[1:]} + + for i, t := range []struct { + should string + schema string + withParams map[string]interface{} + expectedResult map[string]interface{} + expectedError string + }{{ + should: "error with no schema", + expectedError: "schema must be of type object", + }, { + should: "create a map if handed nil", + schema: schemas["none"], + withParams: nil, + expectedResult: map[string]interface{}{}, + }, { + should: "create and fill target if handed nil", + schema: schemas["simple"], + withParams: nil, + expectedResult: map[string]interface{}{"val": "somestr"}, + }, { + should: "create a simple default value", + schema: schemas["simple"], + withParams: map[string]interface{}{}, + expectedResult: map[string]interface{}{"val": "somestr"}, + }, { + should: "do nothing for no default value", + schema: schemas["none"], + withParams: map[string]interface{}{}, + expectedResult: map[string]interface{}{}, + }, { + should: "insert a default value within a nested map", + schema: schemas["complicated"], + withParams: map[string]interface{}{}, + expectedResult: map[string]interface{}{ + "val": map[string]interface{}{ + "bar": map[string]interface{}{ + "baz": "boz", + }}}, + }, { + should: "create a default value which is an object", + schema: schemas["default-object"], + withParams: map[string]interface{}{}, + expectedResult: map[string]interface{}{ + "val": map[string]interface{}{ + "foo": "bar", + "bar": map[string]interface{}{ + "baz": "woz", + }}}, + }, { + should: "not overwrite existing values with default objects", + schema: schemas["default-object"], + withParams: map[string]interface{}{"val": 5}, + expectedResult: map[string]interface{}{"val": 5}, + }, { + should: "interleave defaults into existing objects", + schema: schemas["complicated"], + withParams: map[string]interface{}{ + "val": map[string]interface{}{ + "foo": "bar", + "bar": map[string]interface{}{ + "faz": "foz", + }}}, + expectedResult: map[string]interface{}{ + "val": map[string]interface{}{ + "foo": "bar", + "bar": map[string]interface{}{ + "baz": "boz", + "faz": "foz", + }}}, + }} { + c.Logf("test %d: should %s", i, t.should) + schema := getSchemaForAction(c, t.schema) + // Testing this method + result, err := schema.InsertDefaults(t.withParams) + if t.expectedError != "" { + c.Check(err, gc.ErrorMatches, t.expectedError) + continue + } + c.Assert(err, jc.ErrorIsNil) + c.Check(result, jc.DeepEquals, t.expectedResult) + } +} + +func getSchemaForAction(c *gc.C, wholeSchema string) ActionSpec { + // Load up the YAML schema definition. + reader := bytes.NewReader([]byte(wholeSchema)) + loadedActions, err := ReadActionsYaml("somecharm", reader) + c.Assert(err, gc.IsNil) + // Same action name for all tests, "act". + return loadedActions.ActionSpecs["act"] +} diff --git a/internal/charm/assumes/expression.go b/internal/charm/assumes/expression.go new file mode 100644 index 00000000000..8d926532597 --- /dev/null +++ b/internal/charm/assumes/expression.go @@ -0,0 +1,61 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package assumes + +import "github.com/juju/version/v2" + +var ( + _ Expression = (*FeatureExpression)(nil) + _ Expression = (*CompositeExpression)(nil) +) + +// ExpressionType represents the type of an assumes expression. +type ExpressionType string + +const ( + AnyOfExpression ExpressionType = "any-of" + AllOfExpression ExpressionType = "all-of" +) + +// Expression is an interface implemented by all expression types in this package. +type Expression interface { + Type() ExpressionType +} + +// VersionConstraint describes a constraint for required feature versions. +type VersionConstraint string + +const ( + VersionGTE VersionConstraint = ">=" + VersionLT VersionConstraint = "<" +) + +// FeatureExpression describes a feature that is required by the charm in order +// to be successfully deployed. Feature expressions may additionally specify a +// version constraint. +type FeatureExpression struct { + // The name of the feature. + Name string + + // A feature within an assumes block may optionally specify a version + // constraint. + Constraint VersionConstraint + Version *version.Number + + // The raw, unprocessed version string for serialization purposes. + rawVersion string +} + +// Type implements Expression. +func (FeatureExpression) Type() ExpressionType { return ExpressionType("feature") } + +// CompositeExpression describes a composite expression that applies some +// operator to a sub-expression list. +type CompositeExpression struct { + ExprType ExpressionType + SubExpressions []Expression +} + +// Type implements Expression. +func (expr CompositeExpression) Type() ExpressionType { return expr.ExprType } diff --git a/internal/charm/assumes/package_test.go b/internal/charm/assumes/package_test.go new file mode 100644 index 00000000000..9a145612239 --- /dev/null +++ b/internal/charm/assumes/package_test.go @@ -0,0 +1,14 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package assumes + +import ( + stdtesting "testing" + + gc "gopkg.in/check.v1" +) + +func Test(t *stdtesting.T) { + gc.TestingT(t) +} diff --git a/internal/charm/assumes/parser.go b/internal/charm/assumes/parser.go new file mode 100644 index 00000000000..465e7f609ce --- /dev/null +++ b/internal/charm/assumes/parser.go @@ -0,0 +1,323 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package assumes + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/juju/errors" + "github.com/juju/mgo/v3/bson" + "github.com/juju/version/v2" + "gopkg.in/yaml.v2" +) + +var ( + featureWithoutVersion = regexp.MustCompile(`^[a-z][a-z0-9-]*?[a-z0-9]+$`) + featureWithVersion = regexp.MustCompile(`^([a-z][a-z0-9-]*?[a-z0-9]+)\s*?(>=|<)\s*?([\S\.]+)$`) +) + +// ExpressionTree is a wrapper for representing a (possibly nested) "assumes" +// block declaration. +type ExpressionTree struct { + Expression Expression +} + +// parseAssumesExpressionTree recursively parses an assumes expression tree +// and returns back an Expression instance for it. +// +// The root of the expression tree consists of a list of (potentially nested) +// assumes expressions that form an implicit All-Of composite expression. +// +// For example: +// assumes: +// - foo +// - bar >= 1.42 +// - any-of: ... (nested expr) +// - all-of: ... (nested expr) +func parseAssumesExpressionTree(rootExprList []interface{}) (Expression, error) { + var ( + rootExpr = CompositeExpression{ + ExprType: AllOfExpression, + SubExpressions: make([]Expression, len(rootExprList)), + } + err error + ) + + for i, exprDecl := range rootExprList { + if rootExpr.SubExpressions[i], err = parseAssumesExpr(exprDecl); err != nil { + return nil, errors.Annotatef(err, `parsing expression %d in top level "assumes" block`, i+1) + } + } + + return rootExpr, nil +} + +// parseAssumesExpr returns an Expression instance that corresponds to the +// provided expression declaration. As per the assumes spec, the parser +// supports the following expression types: +// +// 1) feature request expression with optional version constraint (e.g. foo < 1) +// 2) any-of composite expression +// 3) all-of composite expression +func parseAssumesExpr(exprDecl interface{}) (Expression, error) { + // Is it a composite expression? + if exprAsMap, isMap := exprDecl.(map[interface{}]interface{}); isMap { + coercedMap := make(map[string]interface{}) + for key, val := range exprAsMap { + keyStr, ok := key.(string) + if !ok { + return nil, errors.New(`malformed composite expression`) + } + coercedMap[keyStr] = val + } + return parseCompositeExpr(coercedMap) + } else if exprAsMap, isMap := exprDecl.(bson.M); isMap { + coercedMap := make(map[string]interface{}) + for key, val := range exprAsMap { + coercedMap[key] = val + } + return parseCompositeExpr(coercedMap) + } else if exprAsMap, isMap := exprDecl.(map[string]interface{}); isMap { + return parseCompositeExpr(exprAsMap) + } + + // Is it a feature request expression? + if exprAsString, isString := exprDecl.(string); isString { + return parseFeatureExpr(exprAsString) + } + + return nil, errors.New(`expected a feature, "any-of" or "all-of" expression`) +} + +// parseCompositeExpr extracts and returns a CompositeExpression from the +// provided expression declaration. +// +// The EBNF grammar for a composite expression is: +// +// composite-expr-decl: ("any-of"|"all-of") expr-decl-list +// +// expr-decl-list: expr-decl+ +// +// expr-decl: feature-expr-decl | +// composite-expr-decl +// +// The function expects a map with either a "any-of" or "all-of" key and +// a value that is a slice of sub-expressions. +func parseCompositeExpr(exprDecl map[string]interface{}) (CompositeExpression, error) { + if len(exprDecl) != 1 { + return CompositeExpression{}, errors.New("malformed composite expression") + } + + var ( + compositeExpr CompositeExpression + subExprDecls interface{} + err error + ) + + if subExprDecls = exprDecl["any-of"]; subExprDecls != nil { + compositeExpr.ExprType = AnyOfExpression + } else if subExprDecls = exprDecl["all-of"]; subExprDecls != nil { + compositeExpr.ExprType = AllOfExpression + } else { + return CompositeExpression{}, errors.New(`malformed composite expression; expected an "any-of" or "all-of" block`) + } + + subExprDeclList, isList := subExprDecls.([]interface{}) + if !isList { + return CompositeExpression{}, errors.Errorf(`malformed %q expression; expected a list of sub-expressions`, string(compositeExpr.ExprType)) + } + + compositeExpr.SubExpressions = make([]Expression, len(subExprDeclList)) + for i, subExprDecl := range subExprDeclList { + if compositeExpr.SubExpressions[i], err = parseAssumesExpr(subExprDecl); err != nil { + return CompositeExpression{}, errors.Annotatef(err, "parsing %q expression", string(compositeExpr.ExprType)) + } + } + return compositeExpr, nil +} + +// parseFeatureExpr extracts and returns a FeatureExpression from the provided +// expression declaration. +// +// The EBNF grammar for feature expressions is: +// +// feature-expr-decl: feature-ident | +// feature-ident version-constraint version-number +// +// version-constraint: ">=" | "<" +// feature-ident: [a-z][a-z0-9-]*[a-z0-9]+ +// version-number: \d+ (‘.’ \d+ (‘.’ \d+)?)? +// +func parseFeatureExpr(exprDecl string) (FeatureExpression, error) { + exprDecl = strings.TrimSpace(exprDecl) + + // Is this a feature name without a version constraint? + if featureWithoutVersion.MatchString(exprDecl) { + return FeatureExpression{Name: exprDecl}, nil + } + + matches := featureWithVersion.FindAllStringSubmatch(exprDecl, 1) + if len(matches) == 1 { + featName, constraint, versionStr := matches[0][1], matches[0][2], matches[0][3] + ver, err := version.ParseNonStrict(versionStr) + if err != nil { + return FeatureExpression{}, errors.Annotatef(err, "malformed feature expression %q", exprDecl) + } + + return FeatureExpression{ + Name: featName, + Constraint: VersionConstraint(constraint), + Version: &ver, + rawVersion: versionStr, + }, nil + } + + return FeatureExpression{}, errors.Errorf("malformed feature expression %q", exprDecl) +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (tree *ExpressionTree) UnmarshalYAML(unmarshalFn func(interface{}) error) error { + var exprTree []interface{} + if err := unmarshalFn(&exprTree); err != nil { + if _, isTypeErr := err.(*yaml.TypeError); isTypeErr { + return errors.New(`malformed "assumes" block; expected an expression list`) + } + return errors.Annotate(err, "decoding assumes block") + } + + expr, err := parseAssumesExpressionTree(exprTree) + if err != nil { + return errors.Trace(err) + } + tree.Expression = expr + return nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (tree *ExpressionTree) UnmarshalJSON(data []byte) error { + var exprTree []interface{} + if err := json.Unmarshal(data, &exprTree); err != nil { + return errors.Annotate(err, "decoding assumes block") + } + + expr, err := parseAssumesExpressionTree(exprTree) + if err != nil { + return errors.Trace(err) + } + tree.Expression = expr + return nil +} + +// SetBSON implements the bson.Setter interface. +func (tree *ExpressionTree) SetBSON(data bson.Raw) error { + var exprTree []interface{} + if err := data.Unmarshal(&exprTree); err != nil { + return errors.Annotate(err, "decoding assumes block") + } + + expr, err := parseAssumesExpressionTree(exprTree) + if err != nil { + return errors.Trace(err) + } + tree.Expression = expr + return nil +} + +// MarshalYAML implements the yaml.Marshaler interface. +func (tree *ExpressionTree) MarshalYAML() (interface{}, error) { + if tree == nil || tree.Expression == nil { + return nil, nil + } + + return marshalAssumesExpressionTree(tree) +} + +// MarshalJSON implements the json.Marshaler interface. +func (tree *ExpressionTree) MarshalJSON() ([]byte, error) { + if tree == nil || tree.Expression == nil { + return nil, nil + } + + exprList, err := marshalAssumesExpressionTree(tree) + if err != nil { + return nil, errors.Trace(err) + } + return json.Marshal(exprList) +} + +// GetBSON implements the bson.Getter interface. +func (tree *ExpressionTree) GetBSON() (interface{}, error) { + if tree == nil || tree.Expression == nil { + return nil, nil + } + + exprList, err := marshalAssumesExpressionTree(tree) + if err != nil { + return nil, errors.Trace(err) + } + return exprList, nil +} + +func marshalAssumesExpressionTree(tree *ExpressionTree) (interface{}, error) { + // The root of the expression tree (top level of the assumes block) is + // always an implicit "any-of". We need to marshal it into a map and + // extract the expression list. + root, err := marshalExpr(tree.Expression) + if err != nil { + return nil, err + } + + rootMap, ok := root.(map[string]interface{}) + if !ok { + return nil, errors.New(`unexpected serialized output for top-level "assumes" block`) + } + + exprList, ok := rootMap[string(AllOfExpression)] + if !ok { + return nil, errors.New(`unexpected serialized output for top-level "assumes" block`) + } + + return exprList, nil +} + +func marshalExpr(expr Expression) (interface{}, error) { + featExpr, ok := expr.(FeatureExpression) + if ok { + if featExpr.Version == nil { + return featExpr.Name, nil + } + + // If we retained the raw version use that; otherwise convert + // the parsed version to a string. + if featExpr.rawVersion != "" { + return fmt.Sprintf("%s %s %s", featExpr.Name, featExpr.Constraint, featExpr.rawVersion), nil + } + + return fmt.Sprintf("%s %s %s", featExpr.Name, featExpr.Constraint, featExpr.Version.String()), nil + } + + // This is a composite expression + compExpr, ok := expr.(CompositeExpression) + if !ok { + return nil, errors.Errorf("unexpected expression type %s", expr.Type()) + } + + var ( + exprList = make([]interface{}, len(compExpr.SubExpressions)) + err error + ) + + for i, subExpr := range compExpr.SubExpressions { + if exprList[i], err = marshalExpr(subExpr); err != nil { + return nil, err + } + } + + return map[string]interface{}{ + string(compExpr.ExprType): exprList, + }, nil +} diff --git a/internal/charm/assumes/parser_test.go b/internal/charm/assumes/parser_test.go new file mode 100644 index 00000000000..81ae0378001 --- /dev/null +++ b/internal/charm/assumes/parser_test.go @@ -0,0 +1,406 @@ +// Copyright 2011-2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package assumes + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/juju/mgo/v3/bson" + jc "github.com/juju/testing/checkers" + "github.com/juju/version/v2" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" +) + +type ParserSuite struct{} + +var _ = gc.Suite(&ParserSuite{}) + +func (s *ParserSuite) TestNestedExpressionUnmarshalingFromYAML(c *gc.C) { + payload := ` +assumes: + - chips + - any-of: + - guacamole + - salsa + - any-of: + - good-weather + - great-music + - all-of: + - table + - lazy-suzan +`[1:] + + dst := struct { + Assumes *ExpressionTree `yaml:"assumes,omitempty"` + }{} + err := yaml.NewDecoder(strings.NewReader(payload)).Decode(&dst) + c.Assert(err, jc.ErrorIsNil) + + exp := CompositeExpression{ + ExprType: AllOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "chips"}, + CompositeExpression{ + ExprType: AnyOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "guacamole"}, + FeatureExpression{Name: "salsa"}, + CompositeExpression{ + ExprType: AnyOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "good-weather"}, + FeatureExpression{Name: "great-music"}, + }, + }, + }, + }, + CompositeExpression{ + ExprType: AllOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "table"}, + FeatureExpression{Name: "lazy-suzan"}, + }, + }, + }, + } + + c.Assert(dst.Assumes.Expression, gc.DeepEquals, exp) +} + +func (s *ParserSuite) TestNestedExpressionUnmarshalingFromJSON(c *gc.C) { + payload := ` +{ + "assumes": [ + "chips", + { + "any-of": [ + "guacamole", + "salsa", + { + "any-of": [ + "good-weather", + "great-music" + ] + } + ] + }, + { + "all-of": [ + "table", + "lazy-suzan" + ] + } + ] +} +`[1:] + + dst := struct { + Assumes *ExpressionTree `json:"assumes,omitempty"` + }{} + err := json.NewDecoder(strings.NewReader(payload)).Decode(&dst) + c.Assert(err, jc.ErrorIsNil) + + exp := CompositeExpression{ + ExprType: AllOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "chips"}, + CompositeExpression{ + ExprType: AnyOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "guacamole"}, + FeatureExpression{Name: "salsa"}, + CompositeExpression{ + ExprType: AnyOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "good-weather"}, + FeatureExpression{Name: "great-music"}, + }, + }, + }, + }, + CompositeExpression{ + ExprType: AllOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "table"}, + FeatureExpression{Name: "lazy-suzan"}, + }, + }, + }, + } + + c.Assert(dst.Assumes.Expression, gc.DeepEquals, exp) +} + +func (s *ParserSuite) TestVersionlessFeatureExprUnmarshalingFromYAML(c *gc.C) { + payload := ` +assumes: + - chips +`[1:] + + dst := struct { + Assumes *ExpressionTree `yaml:"assumes,omitempty"` + }{} + err := yaml.NewDecoder(strings.NewReader(payload)).Decode(&dst) + c.Assert(err, jc.ErrorIsNil) + + exp := CompositeExpression{ + ExprType: AllOfExpression, + SubExpressions: []Expression{ + FeatureExpression{Name: "chips"}, + }, + } + + c.Assert(dst.Assumes.Expression, gc.DeepEquals, exp) +} + +func (s *ParserSuite) TestVersionedFeatureExprUnmarshaling(c *gc.C) { + payload := ` +assumes: # test various combinations of whitespace and version formats + - chips >= 2000.1.2 + - chips<2042.3.4 + - k8s-api >= 1.8 + - k8s-api < 42 +`[1:] + + dst := struct { + Assumes *ExpressionTree `yaml:"assumes,omitempty"` + }{} + err := yaml.NewDecoder(strings.NewReader(payload)).Decode(&dst) + c.Assert(err, jc.ErrorIsNil) + + exp := CompositeExpression{ + ExprType: AllOfExpression, + SubExpressions: []Expression{ + FeatureExpression{ + Name: "chips", + Constraint: VersionGTE, + Version: &version.Number{ + Major: 2000, + Minor: 1, + Patch: 2, + }, + rawVersion: "2000.1.2", + }, + FeatureExpression{ + Name: "chips", + Constraint: VersionLT, + Version: &version.Number{ + Major: 2042, + Minor: 3, + Patch: 4, + }, + rawVersion: "2042.3.4", + }, + FeatureExpression{ + Name: "k8s-api", + Constraint: VersionGTE, + Version: &version.Number{ + Major: 1, + Minor: 8, + }, + rawVersion: "1.8", + }, + FeatureExpression{ + Name: "k8s-api", + Constraint: VersionLT, + Version: &version.Number{ + Major: 42, + }, + rawVersion: "42", + }, + }, + } + + c.Assert(dst.Assumes.Expression, gc.DeepEquals, exp) +} + +func (s *ParserSuite) TestMalformedCompositeExpression(c *gc.C) { + payload := ` +assumes: + - root: + - access +`[1:] + + dst := struct { + Assumes *ExpressionTree `yaml:"assumes,omitempty"` + }{} + err := yaml.NewDecoder(strings.NewReader(payload)).Decode(&dst) + c.Assert(err, gc.ErrorMatches, `.*expected an "any-of" or "all-of" block.*`) +} + +func (s *ParserSuite) TestFeatureExprParser(c *gc.C) { + specs := []struct { + descr string + in string + expErr string + }{ + { + descr: "feature without version", + in: "k8s", + }, + { + descr: "feature with GTE version constraint", + in: "juju >= 1.2.3", + }, + { + descr: "feature with LT version constraint", + in: "juju < 1.2.3", + }, + { + descr: "feature with incorrect prefix", + in: "0day", + expErr: ".*malformed.*", + }, + { + descr: "feature with incorrect prefix (II)", + in: "-day", + expErr: ".*malformed.*", + }, + { + descr: "feature with incorrect suffix", + in: "a-day-", + expErr: ".*malformed.*", + }, + { + descr: "feature with bogus version constraint", + in: "popcorn = 1.0.0", + expErr: ".*malformed.*", + }, + { + descr: "feature with only major version component", + in: "popcorn >= 1", + }, + { + descr: "feature with only major/minor version component", + in: "popcorn >= 1.2", + }, + } + + for specIdx, spec := range specs { + c.Logf("%d. %s", specIdx, spec.descr) + _, err := parseFeatureExpr(spec.in) + if spec.expErr == "" { + c.Assert(err, jc.ErrorIsNil) + } else { + c.Assert(err, gc.ErrorMatches, spec.expErr) + } + } +} + +func (s *ParserSuite) TestMarshalToYAML(c *gc.C) { + payload := ` +assumes: +- chips +- any-of: + - guacamole + - salsa + - any-of: + - good-weather + - great-music +- all-of: + - table + - lazy-suzan +`[1:] + + dst := struct { + Assumes *ExpressionTree `yaml:"assumes,omitempty"` + }{} + err := yaml.NewDecoder(strings.NewReader(payload)).Decode(&dst) + c.Assert(err, jc.ErrorIsNil) + + var buf bytes.Buffer + err = yaml.NewEncoder(&buf).Encode(dst) + c.Assert(err, jc.ErrorIsNil) + c.Assert(buf.String(), gc.Equals, payload, gc.Commentf("serialized assumes block not matching original input")) +} + +func (s *ParserSuite) TestMarshalToJSON(c *gc.C) { + payload := ` +{ + "assumes": [ + "chips", + { + "any-of": [ + "guacamole", + "salsa", + { + "any-of": [ + "good-weather", + "great-music" + ] + } + ] + }, + { + "all-of": [ + "table", + "lazy-suzan" + ] + } + ] +} +`[1:] + + dst := struct { + Assumes *ExpressionTree `json:"assumes,omitempty"` + }{} + err := json.NewDecoder(strings.NewReader(payload)).Decode(&dst) + c.Assert(err, jc.ErrorIsNil) + + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetIndent("", " ") + err = enc.Encode(dst) + c.Assert(err, jc.ErrorIsNil) + c.Assert(buf.String(), gc.Equals, payload, gc.Commentf("serialized assumes block not matching original input")) +} + +func (s *ParserSuite) TestMarshalToBSONAndBack(c *gc.C) { + payload := ` +{ + "assumes": [ + "chips", + { + "any-of": [ + "guacamole", + "salsa", + { + "any-of": [ + "good-weather", + "great-music" + ] + } + ] + }, + { + "all-of": [ + "table", + "lazy-suzan" + ] + } + ] +} +`[1:] + + src := struct { + Assumes *ExpressionTree `json:"assumes,omitempty" bson:"assumes,omitempty"` + }{} + err := json.NewDecoder(strings.NewReader(payload)).Decode(&src) + c.Assert(err, jc.ErrorIsNil) + + // Marshal to bson + marshaledBSON, err := bson.Marshal(src) + c.Assert(err, jc.ErrorIsNil) + + // Unmarshal expression tree + dst := struct { + Assumes *ExpressionTree `json:"assumes,omitempty" bson:"assumes,omitempty"` + }{} + err = bson.Unmarshal(marshaledBSON, &dst) + c.Assert(err, jc.ErrorIsNil) + c.Assert(src, gc.DeepEquals, dst, gc.Commentf("serialized assumes block not matching original input")) +} diff --git a/internal/charm/base.go b/internal/charm/base.go new file mode 100644 index 00000000000..0aca64f49bb --- /dev/null +++ b/internal/charm/base.go @@ -0,0 +1,101 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "strings" + + "github.com/juju/collections/set" + "github.com/juju/errors" + "github.com/juju/os/v2" + "github.com/juju/utils/v4/arch" +) + +// Base represents an OS/Channel. +// Bases can also be converted to and from a series string. +type Base struct { + Name string `bson:"name,omitempty" json:"name,omitempty"` + Channel Channel `bson:"channel,omitempty" json:"channel,omitempty"` + Architectures []string `bson:"architectures,omitempty" json:"architectures,omitempty"` +} + +// Validate returns with no error when the Base is valid. +func (b Base) Validate() error { + if b.Name == "" { + return errors.NotValidf("base without name") + } + + if !validOSForBase.Contains(b.Name) { + return errors.NotValidf("os %q", b.Name) + } + if b.Channel.Empty() { + return errors.NotValidf("channel") + } + for _, v := range b.Architectures { + if !arch.IsSupportedArch(v) { + return errors.NotValidf("architecture %q", v) + } + } + + return nil +} + +// String representation of the Base. +func (b Base) String() string { + if b.Channel.Empty() { + panic("cannot stringify invalid base. Bases should always be validated before stringifying") + } + str := fmt.Sprintf("%s@%s", b.Name, b.Channel) + if len(b.Architectures) > 0 { + str = fmt.Sprintf("%s on %s", str, strings.Join(b.Architectures, ", ")) + } + return str +} + +// ParseBase parses a base as string in the form "os@track/risk/branch" +// and an optional list of architectures +func ParseBase(s string, archs ...string) (Base, error) { + var err error + base := Base{} + + segments := strings.Split(s, "@") + if len(segments) != 2 { + return Base{}, errors.NotValidf("base string must contain exactly one @. %q", s) + } + base.Name = strings.ToLower(segments[0]) + channelName := segments[1] + + if channelName != "" { + base.Channel, err = ParseChannelNormalize(channelName) + if err != nil { + return Base{}, errors.Annotatef(err, "malformed channel in base string %q", s) + } + } + + base.Architectures = make([]string, len(archs)) + for i, v := range archs { + base.Architectures[i] = arch.NormaliseArch(v) + } + + err = base.Validate() + if err != nil { + var a string + if len(base.Architectures) > 0 { + a = fmt.Sprintf(" with architectures %q", strings.Join(base.Architectures, ",")) + } + return Base{}, errors.Annotatef(err, "invalid base string %q%s", s, a) + } + return base, nil +} + +// validOSForBase is a string set of valid OS names for a base. +var validOSForBase = set.NewStrings( + strings.ToLower(os.Ubuntu.String()), + strings.ToLower(os.CentOS.String()), + strings.ToLower(os.Windows.String()), + strings.ToLower(os.OSX.String()), + strings.ToLower(os.OpenSUSE.String()), + strings.ToLower(os.GenericLinux.String()), +) diff --git a/internal/charm/base_test.go b/internal/charm/base_test.go new file mode 100644 index 00000000000..34d6edc7321 --- /dev/null +++ b/internal/charm/base_test.go @@ -0,0 +1,177 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "encoding/json" + "strings" + + "github.com/juju/os/v2" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils/v4/arch" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +type baseSuite struct { + testing.CleanupSuite +} + +var _ = gc.Suite(&baseSuite{}) + +func (s *baseSuite) TestParseBase(c *gc.C) { + tests := []struct { + str string + parsedBase charm.Base + err string + }{ + { + str: "ubuntu", + parsedBase: charm.Base{}, + err: `base string must contain exactly one @. "ubuntu" not valid`, + }, { + str: "windows", + parsedBase: charm.Base{}, + err: `base string must contain exactly one @. "windows" not valid`, + }, { + str: "mythicalos@channel", + parsedBase: charm.Base{}, + err: `invalid base string "mythicalos@channel": os "mythicalos" not valid`, + }, { + str: "ubuntu@20.04/stable", + parsedBase: charm.Base{Name: strings.ToLower(os.Ubuntu.String()), Channel: mustParseChannel("20.04/stable")}, + }, { + str: "windows@win10/stable", + parsedBase: charm.Base{Name: strings.ToLower(os.Windows.String()), Channel: mustParseChannel("win10/stable")}, + }, { + str: "ubuntu@20.04/edge", + parsedBase: charm.Base{Name: strings.ToLower(os.Ubuntu.String()), Channel: mustParseChannel("20.04/edge")}, + }, + } + for i, v := range tests { + comment := gc.Commentf("test %d", i) + s, err := charm.ParseBase(v.str) + if v.err != "" { + c.Check(err, gc.ErrorMatches, v.err, comment) + } else { + c.Check(err, jc.ErrorIsNil, comment) + } + c.Check(s, jc.DeepEquals, v.parsedBase, comment) + } +} + +func (s *baseSuite) TestParseBaseWithArchitectures(c *gc.C) { + tests := []struct { + str string + baseString string + archs []string + parsedBase charm.Base + err string + }{ + { + baseString: "ubuntu@", + str: "ubuntu on amd64", + archs: []string{"amd64"}, + parsedBase: charm.Base{}, + err: `invalid base string "ubuntu@" with architectures "amd64": channel not valid`, + }, { + baseString: "windows@", + str: "windows", + parsedBase: charm.Base{}, + err: `invalid base string "windows@": channel not valid`, + }, { + baseString: "mythicalos@channel", + str: "mythicalos", + parsedBase: charm.Base{}, + err: `invalid base string "mythicalos@channel": os "mythicalos" not valid`, + }, { + baseString: "ubuntu@20.04/stable", + archs: []string{arch.AMD64, "ppc64"}, + str: "ubuntu@20.04/stable on amd64, ppc64el", + parsedBase: charm.Base{ + Name: strings.ToLower(os.Ubuntu.String()), + Channel: mustParseChannel("20.04/stable"), + Architectures: []string{arch.AMD64, arch.PPC64EL}}, + }, { + baseString: "windows@win10/stable", + archs: []string{"testme"}, + str: "windows@win10/stable", + parsedBase: charm.Base{}, + err: `invalid base string "windows@win10/stable" with architectures "testme": architecture "testme" not valid`, + }, + } + for i, v := range tests { + comment := gc.Commentf("test %d", i) + s, err := charm.ParseBase(v.baseString, v.archs...) + if v.err != "" { + c.Check(err, gc.ErrorMatches, v.err, comment) + } else { + c.Check(err, jc.ErrorIsNil, comment) + } + c.Check(s, jc.DeepEquals, v.parsedBase, comment) + } +} + +func (s *baseSuite) TestStringifyBase(c *gc.C) { + tests := []struct { + base charm.Base + str string + }{ + { + base: charm.Base{Name: strings.ToLower(os.Ubuntu.String()), Channel: mustParseChannel("20.04/stable")}, + str: "ubuntu@20.04/stable", + }, { + base: charm.Base{Name: strings.ToLower(os.Windows.String()), Channel: mustParseChannel("win10/stable")}, + str: "windows@win10/stable", + }, { + base: charm.Base{Name: strings.ToLower(os.Ubuntu.String()), Channel: mustParseChannel("20.04/edge")}, + str: "ubuntu@20.04/edge", + }, { + base: charm.Base{ + Name: strings.ToLower(os.Ubuntu.String()), + Channel: mustParseChannel("20.04/stable"), + Architectures: []string{arch.AMD64}, + }, + str: "ubuntu@20.04/stable on amd64", + }, { + base: charm.Base{ + Name: strings.ToLower(os.Ubuntu.String()), + Channel: mustParseChannel("20.04/stable"), + Architectures: []string{arch.AMD64, arch.PPC64EL}, + }, + str: "ubuntu@20.04/stable on amd64, ppc64el", + }, + } + for i, v := range tests { + comment := gc.Commentf("test %d", i) + c.Assert(v.base.Validate(), jc.ErrorIsNil) + c.Assert(v.base.String(), gc.Equals, v.str, comment) + } +} + +func (s *baseSuite) TestJSONEncoding(c *gc.C) { + sys := charm.Base{ + Name: "ubuntu", + Channel: mustParseChannel("20.04/stable"), + } + bytes, err := json.Marshal(sys) + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(bytes), gc.Equals, `{"name":"ubuntu","channel":{"track":"20.04","risk":"stable"}}`) + sys2 := charm.Base{} + err = json.Unmarshal(bytes, &sys2) + c.Assert(err, jc.ErrorIsNil) + c.Assert(sys2, jc.DeepEquals, sys) +} + +// MustParseChannel parses a given string or returns a panic. +// Used for unit tests. +func mustParseChannel(s string) charm.Channel { + c, err := charm.ParseChannelNormalize(s) + if err != nil { + panic(err) + } + return c +} diff --git a/internal/charm/bundle.go b/internal/charm/bundle.go new file mode 100644 index 00000000000..9f43c335320 --- /dev/null +++ b/internal/charm/bundle.go @@ -0,0 +1,43 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "os" + "path/filepath" + "strings" +) + +// The Bundle interface is implemented by any type that +// may be handled as a bundle. It encapsulates all +// the data of a bundle. +type Bundle interface { + // Data returns the contents of the bundle's bundle.yaml file. + Data() *BundleData + // BundleBytes returns the raw bytes content of a bundle + BundleBytes() []byte + // ReadMe returns the contents of the bundle's README.md file. + ReadMe() string + // ContainsOverlays returns true if the bundle contains any overlays. + ContainsOverlays() bool +} + +// ReadBundle reads a Bundle from path, which can point to either a +// bundle archive or a bundle directory. +func ReadBundle(path string) (Bundle, error) { + info, err := os.Stat(path) + if err != nil { + return nil, err + } + if info.IsDir() { + return ReadBundleDir(path) + } + return ReadBundleArchive(path) +} + +// IsValidLocalCharmOrBundlePath returns true if path is valid for reading a +// local charm or bundle. +func IsValidLocalCharmOrBundlePath(path string) bool { + return strings.HasPrefix(path, ".") || filepath.IsAbs(path) +} diff --git a/internal/charm/bundle_test.go b/internal/charm/bundle_test.go new file mode 100644 index 00000000000..576cea13755 --- /dev/null +++ b/internal/charm/bundle_test.go @@ -0,0 +1,102 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "os" + "path/filepath" + + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +var _ = gc.Suite(&BundleSuite{}) + +type BundleSuite struct { + testing.IsolationSuite +} + +func (*BundleSuite) TestReadBundleDir(c *gc.C) { + path := bundleDirPath(c, "wordpress-simple") + b, err := charm.ReadBundle(path) + c.Assert(err, gc.IsNil) + c.Assert(b.ContainsOverlays(), jc.IsFalse) + c.Assert(b, gc.FitsTypeOf, (*charm.BundleDir)(nil)) + checkWordpressBundle(c, b, path, "wordpress-simple") +} + +func (*BundleSuite) TestReadMultiDocBundleDir(c *gc.C) { + path := bundleDirPath(c, "wordpress-simple-multidoc") + b, err := charm.ReadBundle(path) + c.Assert(err, gc.IsNil) + c.Assert(b.ContainsOverlays(), jc.IsTrue) + c.Assert(b, gc.FitsTypeOf, (*charm.BundleDir)(nil)) + checkWordpressBundle(c, b, path, "wordpress-simple-multidoc") +} + +func (*BundleSuite) TestReadBundleArchive(c *gc.C) { + path := bundleDirPath(c, "wordpress-simple") + b, err := charm.ReadBundle(path) + c.Assert(err, gc.IsNil) + c.Assert(b.ContainsOverlays(), jc.IsFalse) + c.Assert(b, gc.FitsTypeOf, (*charm.BundleDir)(nil)) + checkWordpressBundle(c, b, path, "wordpress-simple") +} + +func (*BundleSuite) TestReadMultiDocBundleArchive(c *gc.C) { + path := bundleDirPath(c, "wordpress-simple-multidoc") + b, err := charm.ReadBundle(path) + c.Assert(err, gc.IsNil) + c.Assert(b.ContainsOverlays(), jc.IsTrue) + c.Assert(b, gc.FitsTypeOf, (*charm.BundleDir)(nil)) + checkWordpressBundle(c, b, path, "wordpress-simple-multidoc") +} + +func checkWordpressBundle(c *gc.C, b charm.Bundle, path string, bundleName string) { + // Load the charms required by the bundle. + wordpressCharm := readCharmDir(c, "wordpress") + mysqlCharm := readCharmDir(c, "mysql") + + bd := b.Data() + c.Assert(bd.RequiredCharms(), jc.DeepEquals, []string{"ch:mysql", "ch:wordpress"}) + + charms := map[string]charm.Charm{ + "ch:wordpress": wordpressCharm, + "ch:mysql": mysqlCharm, + } + err := bd.VerifyWithCharms(verifyOk, nil, nil, charms) + c.Assert(err, gc.IsNil) + + c.Assert(bd.Applications, jc.DeepEquals, map[string]*charm.ApplicationSpec{ + "wordpress": { + Charm: "ch:wordpress", + }, + "mysql": { + Charm: "ch:mysql", + NumUnits: 1, + }, + }) + c.Assert(bd.Relations, jc.DeepEquals, [][]string{ + {"wordpress:db", "mysql:server"}, + }) + c.Assert(b.ReadMe(), gc.Equals, "A dummy bundle\n") + switch b := b.(type) { + case *charm.BundleArchive: + c.Assert(b.Path, gc.Equals, path) + case *charm.BundleDir: + c.Assert(b.Path, gc.Equals, path) + } + + bundlePath := filepath.Join("internal/test-charm-repo/bundle", bundleName, "bundle.yaml") + raw, err := os.ReadFile(bundlePath) + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(b.BundleBytes()), gc.Equals, string(raw)) +} + +func verifyOk(string) error { + return nil +} diff --git a/internal/charm/bundlearchive.go b/internal/charm/bundlearchive.go new file mode 100644 index 00000000000..4a2feda7a30 --- /dev/null +++ b/internal/charm/bundlearchive.go @@ -0,0 +1,116 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "bytes" + "io" + + ziputil "github.com/juju/utils/v4/zip" +) + +type BundleArchive struct { + zopen zipOpener + + Path string + data *BundleData + bundleBytes []byte + readMe string + + containsOverlays bool +} + +// ReadBundleArchive reads a bundle archive from the given file path. +func ReadBundleArchive(path string) (*BundleArchive, error) { + a, err := readBundleArchive(newZipOpenerFromPath(path)) + if err != nil { + return nil, err + } + a.Path = path + return a, nil +} + +// ReadBundleArchiveBytes reads a bundle archive from the given byte +// slice. +func ReadBundleArchiveBytes(data []byte) (*BundleArchive, error) { + zopener := newZipOpenerFromReader(bytes.NewReader(data), int64(len(data))) + return readBundleArchive(zopener) +} + +// ReadBundleArchiveFromReader returns a BundleArchive that uses +// r to read the bundle. The given size must hold the number +// of available bytes in the file. +// +// Note that the caller is responsible for closing r - methods on +// the returned BundleArchive may fail after that. +func ReadBundleArchiveFromReader(r io.ReaderAt, size int64) (*BundleArchive, error) { + return readBundleArchive(newZipOpenerFromReader(r, size)) +} + +func readBundleArchive(zopen zipOpener) (*BundleArchive, error) { + a := &BundleArchive{ + zopen: zopen, + } + zipr, err := zopen.openZip() + if err != nil { + return nil, err + } + defer zipr.Close() + reader, err := zipOpenFile(zipr, "bundle.yaml") + if err != nil { + return nil, err + } + b, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + a.bundleBytes = b + a.data, a.containsOverlays, err = readBaseFromMultidocBundle(b) + reader.Close() + if err != nil { + return nil, err + } + reader, err = zipOpenFile(zipr, "README.md") + if err != nil { + return nil, err + } + readMe, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + a.readMe = string(readMe) + return a, nil +} + +// Data implements Bundle.Data. +func (a *BundleArchive) Data() *BundleData { + return a.data +} + +// BundleBytes implements Bundle.BundleBytes. +func (a *BundleArchive) BundleBytes() []byte { + return a.bundleBytes +} + +// ReadMe implements Bundle.ReadMe. +func (a *BundleArchive) ReadMe() string { + return a.readMe +} + +// ContainsOverlays implements Bundle.ReadMe. +func (a *BundleArchive) ContainsOverlays() bool { + return a.containsOverlays +} + +// ExpandTo expands the bundle archive into dir, creating it if necessary. +// If any errors occur during the expansion procedure, the process will +// abort. +func (a *BundleArchive) ExpandTo(dir string) error { + zipr, err := a.zopen.openZip() + if err != nil { + return err + } + defer zipr.Close() + return ziputil.ExtractAll(zipr.Reader, dir) +} diff --git a/internal/charm/bundlearchive_test.go b/internal/charm/bundlearchive_test.go new file mode 100644 index 00000000000..d39b1f98d04 --- /dev/null +++ b/internal/charm/bundlearchive_test.go @@ -0,0 +1,113 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "fmt" + "os" + "path/filepath" + + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +var _ = gc.Suite(&BundleArchiveSuite{}) + +type BundleArchiveSuite struct { + archivePath string +} + +const bundleName = "wordpress-simple" + +func (s *BundleArchiveSuite) SetUpSuite(c *gc.C) { + s.archivePath = archivePath(c, readBundleDir(c, bundleName)) +} + +func (s *BundleArchiveSuite) TestReadBundleArchive(c *gc.C) { + archive, err := charm.ReadBundleArchive(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + checkWordpressBundle(c, archive, s.archivePath, bundleName) +} + +func (s *BundleArchiveSuite) TestReadBundleArchiveBytes(c *gc.C) { + data, err := os.ReadFile(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadBundleArchiveBytes(data) + c.Assert(err, jc.ErrorIsNil) + c.Assert(archive.ContainsOverlays(), jc.IsFalse) + checkWordpressBundle(c, archive, "", bundleName) +} + +func (s *BundleArchiveSuite) TestReadMultiDocBundleArchiveBytes(c *gc.C) { + path := archivePath(c, readBundleDir(c, "wordpress-simple-multidoc")) + data, err := os.ReadFile(path) + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadBundleArchiveBytes(data) + c.Assert(err, jc.ErrorIsNil) + c.Assert(archive.ContainsOverlays(), jc.IsTrue) + checkWordpressBundle(c, archive, "", "wordpress-simple-multidoc") +} + +func (s *BundleArchiveSuite) TestReadBundleArchiveFromReader(c *gc.C) { + f, err := os.Open(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + defer f.Close() + info, err := f.Stat() + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadBundleArchiveFromReader(f, info.Size()) + c.Assert(err, jc.ErrorIsNil) + checkWordpressBundle(c, archive, "", bundleName) +} + +func (s *BundleArchiveSuite) TestReadBundleArchiveWithoutBundleYAML(c *gc.C) { + testReadBundleArchiveWithoutFile(c, "bundle.yaml") +} + +func (s *BundleArchiveSuite) TestReadBundleArchiveWithoutREADME(c *gc.C) { + testReadBundleArchiveWithoutFile(c, "README.md") +} + +func testReadBundleArchiveWithoutFile(c *gc.C, fileToRemove string) { + path := cloneDir(c, bundleDirPath(c, "wordpress-simple")) + dir, err := charm.ReadBundleDir(path) + c.Assert(err, jc.ErrorIsNil) + + // Remove the file from the bundle directory. + // ArchiveTo just zips the contents of the directory as-is, + // so the resulting bundle archive not contain the + // file. + err = os.Remove(filepath.Join(dir.Path, fileToRemove)) + c.Assert(err, jc.ErrorIsNil) + + archivePath := filepath.Join(c.MkDir(), "out.bundle") + dstf, err := os.Create(archivePath) + c.Assert(err, jc.ErrorIsNil) + + err = dir.ArchiveTo(dstf) + c.Assert(err, jc.ErrorIsNil) + dstf.Close() + + archive, err := charm.ReadBundleArchive(archivePath) + // Slightly dubious assumption: the quoted file name has no + // regexp metacharacters worth worrying about. + c.Assert(err, gc.ErrorMatches, fmt.Sprintf("archive file %q not found", fileToRemove)) + c.Assert(archive, gc.IsNil) +} + +func (s *BundleArchiveSuite) TestExpandTo(c *gc.C) { + dir := c.MkDir() + archive, err := charm.ReadBundleArchive(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + err = archive.ExpandTo(dir) + c.Assert(err, jc.ErrorIsNil) + bdir, err := charm.ReadBundleDir(dir) + c.Assert(err, jc.ErrorIsNil) + c.Assert(bdir.ReadMe(), gc.Equals, archive.ReadMe()) + c.Assert(bdir.Data(), gc.DeepEquals, archive.Data()) +} diff --git a/internal/charm/bundledata.go b/internal/charm/bundledata.go new file mode 100644 index 00000000000..89d28a3b100 --- /dev/null +++ b/internal/charm/bundledata.go @@ -0,0 +1,1281 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "encoding/json" + "fmt" + "io" + "net" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/juju/errors" + "github.com/juju/mgo/v3/bson" + "github.com/juju/names/v5" + "github.com/juju/utils/v4/keyvalues" +) + +const kubernetes = "kubernetes" + +// BundleData holds the contents of the bundle. +type BundleData struct { + // Type is used to signify whether this bundle is for IAAS or Kubernetes deployments. + // Valid values are "Kubernetes" or "", with empty signifying an IAAS bundle. + Type string `bson:"bundle,omitempty" json:"bundle,omitempty" yaml:"bundle,omitempty"` + + // Applications holds one entry for each application + // that the bundle will create, indexed by + // the application name. + Applications map[string]*ApplicationSpec `bson:"applications,omitempty" json:"applications,omitempty" yaml:"applications,omitempty"` + + // Machines holds one entry for each machine referred to + // by unit placements. These will be mapped onto actual + // machines at bundle deployment time. + // It is an error if a machine is specified but + // not referred to by a unit placement directive. + Machines map[string]*MachineSpec `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // Saas holds one entry for each software as a service (SAAS) for cross + // model relation (CMR). These will be mapped to the consuming side when + // deploying a bundle. + Saas map[string]*SaasSpec `bson:"saas,omitempty" json:"saas,omitempty" yaml:"saas,omitempty"` + + // Series holds the default series to use when + // the bundle deploys applications. A series defined for an application + // takes precedence. + Series string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // Base holds the default base to use when the bundle deploys + // applications. A base defined for an application takes precedence. + DefaultBase string `bson:"default-base,omitempty" json:"default-base,omitempty" yaml:"default-base,omitempty"` + + // Relations holds a slice of 2-element slices, + // each specifying a relation between two applications. + // Each two-element slice holds two endpoints, + // each specified as either colon-separated + // (application, relation) pair or just an application name. + // The relation is made between each. If the relation + // name is omitted, it will be inferred from the available + // relations defined in the applications' charms. + Relations [][]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // White listed set of tags to categorize bundles as we do charms. + Tags []string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // Short paragraph explaining what the bundle is useful for. + Description string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` +} + +// SaasSpec represents a single software as a service (SAAS) node. +// This will be mapped to consuming of offers from a bundle deployment. +type SaasSpec struct { + URL string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` +} + +// MachineSpec represents a notional machine that will be mapped +// onto an actual machine at bundle deployment time. +type MachineSpec struct { + Constraints string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + Annotations map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + Series string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + Base string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` +} + +// ApplicationSpec represents a single application that will +// be deployed as part of the bundle. +type ApplicationSpec struct { + // Charm holds the charm URL of the charm to + // use for the given application. + Charm string `bson:",omitempty" yaml:",omitempty" json:",omitempty"` + + // Channel describes the preferred channel to use when deploying a + // remote charm. + Channel string `bson:"channel,omitempty" yaml:"channel,omitempty" json:"channel,omitempty"` + + // Revision describes the revision of the charm to use when deploying. + Revision *int `bson:"revision,omitempty" yaml:"revision,omitempty" json:"revision,omitempty"` + + // Series is the series to use when deploying the application. + Series string `bson:",omitempty" yaml:",omitempty" json:",omitempty"` + + // Base is the base to use when deploying the application. + Base string `bson:",omitempty" yaml:",omitempty" json:",omitempty"` + + // Resources is the set of resource revisions to deploy for the + // application. Bundles only support charm store resources and not ones + // that were uploaded to the controller. + // A resource value can either be an integer revision number, + // or a string holding a path to a local resource file. + Resources map[string]interface{} `bson:",omitempty" yaml:",omitempty" json:",omitempty"` + + // NumUnits holds the number of units of the + // application that will be deployed. + // For Kubernetes bundles, this will be an alias for Scale. + // + // For a subordinate application, this actually represents + // an arbitrary number of units depending on + // the application it is related to. + NumUnits int `bson:",omitempty" yaml:"num_units,omitempty" json:",omitempty"` + + // Scale_ holds the number of pods required for the application. + // For IAAS bundles, this will be an alias for NumUnits. + Scale_ int `bson:"scale,omitempty" yaml:"scale,omitempty" json:"scale,omitempty"` + + // To is interpreted according to whether this is an + // IAAS or Kubernetes bundle. + // + // For Kubernetes bundles, the use of Placement is preferred. + // To must be a single valued list representing label key values + // used as a node selector. + // + // For IAAS bundles, To may hold up to NumUnits members with + // each member specifying a desired placement + // for the respective unit of the application. + // + // In regular-expression-like notation, each + // element matches the following pattern: + // + // (:)?(||new) + // + // If containertype is specified, the unit is deployed + // into a new container of that type, otherwise + // it will be "hulk-smashed" into the specified location, + // by co-locating it with any other units that happen to + // be there, which may result in unintended behavior. + // + // The second part (after the colon) specifies where + // the new unit should be placed - it may refer to + // a unit of another application specified in the bundle, + // a machine id specified in the machines section, + // or the special name "new" which specifies a newly + // created machine. + // + // A unit placement may be specified with an application name only, + // in which case its unit number is assumed to + // be one more than the unit number of the previous + // unit in the list with the same application, or zero + // if there were none. + // + // If there are less elements in To than NumUnits, + // the last element is replicated to fill it. If there + // are no elements (or To is omitted), "new" is replicated. + // + // For example: + // + // wordpress/0 wordpress/1 lxc:0 kvm:new + // + // specifies that the first two units get hulk-smashed + // onto the first two units of the wordpress application, + // the third unit gets allocated onto an lxc container + // on machine 0, and subsequent units get allocated + // on kvm containers on new machines. + // + // The above example is the same as this: + // + // wordpress wordpress lxc:0 kvm:new + To []string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // Placement_ holds a model selector/affinity expression used to specify + // pod placement for Kubernetes applications. + // Not relevant for IAAS applications. + Placement_ string `bson:"placement,omitempty" json:"placement,omitempty" yaml:"placement,omitempty"` + + // Expose holds whether the application must be exposed. + Expose bool `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // ExposedEndpoints defines on a per-endpoint basis, the list of space + // names and/or CIDRs that should be able to access the ports opened + // for an endpoint once the application is exposed. The keys of the map + // are endpoint names or the special empty ("") value that is used as a + // placeholder for referring to all endpoints. + // + // This attribute cannot be used in tandem with the 'expose: true' + // flag; a validation error will be raised if both fields are specified. + ExposedEndpoints map[string]ExposedEndpointSpec `bson:"exposed-endpoints,omitempty" json:"exposed-endpoints,omitempty" yaml:"exposed-endpoints,omitempty" source:"overlay-only"` + + // Options holds the configuration values + // to apply to the new application. They should + // be compatible with the charm configuration. + Options map[string]interface{} `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // Annotations holds any annotations to apply to the + // application when deployed. + Annotations map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // Constraints holds the default constraints to apply + // when creating new machines for units of the application. + // This is ignored for units with explicit placement directives. + Constraints string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // Storage holds the constraints for storage to assign + // to units of the application. + Storage map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // Devices holds the constraints for devices to assign + // to units of the application. + Devices map[string]string `bson:",omitempty" json:",omitempty" yaml:",omitempty"` + + // EndpointBindings maps how endpoints are bound to spaces + EndpointBindings map[string]string `bson:"bindings,omitempty" json:"bindings,omitempty" yaml:"bindings,omitempty"` + + // Offers holds one entry for each exported offer for this application + // where the key is the offer name. + Offers map[string]*OfferSpec `bson:"offers,omitempty" json:"offers,omitempty" yaml:"offers,omitempty" source:"overlay-only"` + + // Plan specifies the plan under which the application is to be deployed. + // If "default", the default plan will be used for the charm + Plan string `bson:"plan,omitempty" json:"plan,omitempty" yaml:"plan,omitempty"` + + // RequiresTrust indicates that the application requires access to + // cloud credentials and must therefore be explicitly trusted by the + // operator before it can be deployed. + RequiresTrust bool `bson:"trust,omitempty" json:"trust,omitempty" yaml:"trust,omitempty"` +} + +// maskedBundleData and bundleData are here to perform a way to normalize the +// bundle data when unmarshalling via a codec. +// By abusing the types we can prevent a recursive function call so that the +// unmarshalling doesn't call itself. +// +// In reality this is so wrong in so many ways: +// 1. Why has the model type got anything to do with how it's transferred over +// the wire to other consumables? The bundle data should have a package of +// wire protocols (DTOs) that can packed and unpacked into a bundle/charm +// model. That model should be pure! +// 2. This should be a two step process, unmarshal and then normalize. +type maskedBundleData BundleData + +type bundleData struct { + maskedBundleData `bson:",inline" yaml:",inline" json:",inline"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (bd *BundleData) UnmarshalJSON(b []byte) error { + var in bundleData + if err := json.Unmarshal(b, &in); err != nil { + return err + } + *bd = BundleData(in.maskedBundleData) + return bd.normalizeData() +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (bd *BundleData) UnmarshalYAML(f func(interface{}) error) error { + var in bundleData + if err := f(&in); err != nil { + return err + } + *bd = BundleData(in.maskedBundleData) + return bd.normalizeData() +} + +// SetBSON implements the bson.Setter interface. +func (bd *BundleData) SetBSON(raw bson.Raw) error { + // TODO(wallyworld) - bson deserialisation is not handling the inline directive, + // so we need to unmarshal the bundle data manually. + var in *bundleData + if err := raw.Unmarshal(&in); err != nil { + return err + } + if in == nil { + return bson.SetZero + } + *bd = BundleData(in.maskedBundleData) + return bd.normalizeData() +} + +func (bd *BundleData) normalizeData() error { + if bd.Applications == nil { + return nil + } + + for appName, app := range bd.Applications { + if app == nil { + continue + } + // Kubernetes bundles use "scale" instead of "num_units". + if app.Scale_ > 0 && app.NumUnits > 0 { + return fmt.Errorf("cannot specify both scale and num_units for application %q", appName) + } + if app.Scale_ > 0 && app.NumUnits == 0 { + app.NumUnits = app.Scale_ + app.Scale_ = 0 + } + // Non-Kubernetes bundles do not use the placement attribute. + if bd.Type != kubernetes && app.Placement_ != "" { + return fmt.Errorf("placement (%s) not valid for non-Kubernetes application %q", app.Placement_, appName) + } + // Kubernetes bundles only use a single placement directive. + if app.Placement_ != "" { + if len(app.To) > 0 { + return fmt.Errorf("cannot specify both placement and to for application %q", appName) + } + app.To = []string{app.Placement_} + app.Placement_ = "" + } + } + return nil +} + +// ExposedEndpointSpec describes the expose parameters for an application +// endpoint. +type ExposedEndpointSpec struct { + // ExposeToSpaces contains a list of spaces that should be able to + // access the application ports opened for an endpoint when the + // application is exposed. + ExposeToSpaces []string `bson:"expose-to-spaces,omitempty" json:"expose-to-spaces,omitempty" yaml:"expose-to-spaces,omitempty" source:"overlay-only"` + + // ExposeToCIDRs contains a list of CIDRs that should be able to access + // the application ports opened for an endpoint when the application is + // exposed. + ExposeToCIDRs []string `bson:"expose-to-cidrs,omitempty" json:"expose-to-cidrs,omitempty" yaml:"expose-to-cidrs,omitempty" source:"overlay-only"` +} + +// OfferSpec describes an offer for a particular application. +type OfferSpec struct { + // The list of endpoints exposed via the offer. + Endpoints []string `bson:"endpoints" json:"endpoints" yaml:"endpoints" source:"overlay-only"` + + // The access control list for this offer. The keys are users and the + // values are access permissions. + ACL map[string]string `bson:"acl,omitempty" json:"acl,omitempty" yaml:"acl,omitempty" source:"overlay-only"` +} + +// ReadBundleData reads bundle data from the given reader. +// The returned data is not verified - call Verify to ensure +// that it is OK. +func ReadBundleData(r io.Reader) (*BundleData, error) { + b, err := io.ReadAll(r) + if err != nil { + return nil, err + } + bd, _, err := readBaseFromMultidocBundle(b) + if err != nil { + return nil, err + } + + return bd, nil +} + +// readBaseFromMultidocBundle reads the bundle data corresponding to the first +// (base) bundle off the given reader. The function returns a boolean flag to +// indicate whether the bundle contains additional documents that the parser +// ignored. +// +// Clients that are interested in reading multi-doc bundle data should use the +// new helpers: LocalBundleDataSource and StreamBundleDataSource. +func readBaseFromMultidocBundle(b []byte) (*BundleData, bool, error) { + parts, err := parseBundleParts(b) + if err != nil { + return nil, false, err + } + + if len(parts) == 0 { + return nil, false, errors.NotValidf("empty bundle") + } + + return parts[0].Data, len(parts) > 1, nil +} + +// VerificationError holds an error generated by BundleData.Verify, +// holding all the verification errors found when verifying. +type VerificationError struct { + Errors []error +} + +func (err *VerificationError) Error() string { + switch len(err.Errors) { + case 0: + return "no verification errors!" + case 1: + return err.Errors[0].Error() + } + return fmt.Sprintf("%s (and %d more errors)", err.Errors[0], len(err.Errors)-1) +} + +type bundleDataVerifier struct { + // bundleDir is the directory containing the bundle file + bundleDir string + bd *BundleData + + // machines holds the reference counts of all machines + // as referred to by placement directives. + machineRefCounts map[string]int + + charms map[string]Charm + + errors []error + verifyConstraints func(c string) error + verifyStorage func(s string) error + verifyDevices func(s string) error +} + +func (verifier *bundleDataVerifier) addErrorf(f string, a ...interface{}) { + verifier.addError(fmt.Errorf(f, a...)) +} + +func (verifier *bundleDataVerifier) addError(err error) { + verifier.errors = append(verifier.errors, err) +} + +func (verifier *bundleDataVerifier) err() error { + if len(verifier.errors) > 0 { + return &VerificationError{verifier.errors} + } + return nil +} + +// RequiredCharms returns a sorted slice of all the charm URLs +// required by the bundle. +func (bd *BundleData) RequiredCharms() []string { + req := make([]string, 0, len(bd.Applications)) + for _, svc := range bd.Applications { + req = append(req, svc.Charm) + } + sort.Strings(req) + return req +} + +// VerifyLocal verifies that a local bundle file is consistent. +// A local bundle file may contain references to charms which are +// referred to by a directory, either relative or absolute. +// +// bundleDir is used to construct the full path for charms specified +// using a relative directory path. The charm path is therefore expected +// to be relative to the bundle.yaml file. +func (bd *BundleData) VerifyLocal( + bundleDir string, + verifyConstraints func(c string) error, + verifyStorage func(s string) error, + verifyDevices func(s string) error, +) error { + return bd.verifyBundle(bundleDir, verifyConstraints, verifyStorage, verifyDevices, nil) +} + +// Verify is a convenience method that calls VerifyWithCharms +// with a nil charms map. +func (bd *BundleData) Verify( + verifyConstraints func(c string) error, + verifyStorage func(s string) error, + verifyDevices func(s string) error, +) error { + return bd.VerifyWithCharms(verifyConstraints, verifyStorage, verifyDevices, nil) +} + +// VerifyWithCharms verifies that the bundle is consistent. +// The verifyConstraints function is called to verify any constraints +// that are found. If verifyConstraints is nil, no checking +// of constraints will be done. Similarly, a non-nil verifyStorage, verifyDevices +// function is called to verify any storage constraints. +// +// It verifies the following: +// +// - All defined machines are referred to by placement directives. +// - All applications referred to by placement directives are specified in the bundle. +// - All applications referred to by relations are specified in the bundle. +// - All basic constraints are valid. +// - All storage constraints are valid. +// +// If charms is not nil, it should hold a map with an entry for each +// charm url returned by bd.RequiredCharms. The verification will then +// also check that applications are defined with valid charms, +// relations are correctly made and options are defined correctly. +// +// If the verification fails, Verify returns a *VerificationError describing +// all the problems found. +func (bd *BundleData) VerifyWithCharms( + verifyConstraints func(c string) error, + verifyStorage func(s string) error, + verifyDevices func(s string) error, + charms map[string]Charm, +) error { + return bd.verifyBundle("", verifyConstraints, verifyStorage, verifyDevices, charms) +} + +func (bd *BundleData) verifyBundle( + bundleDir string, + verifyConstraints func(c string) error, + verifyStorage func(s string) error, + verifyDevices func(s string) error, + charms map[string]Charm, +) error { + if verifyConstraints == nil { + verifyConstraints = func(string) error { + return nil + } + } + if verifyStorage == nil { + verifyStorage = func(string) error { + return nil + } + } + if verifyDevices == nil { + verifyDevices = func(string) error { + return nil + } + } + verifier := &bundleDataVerifier{ + bundleDir: bundleDir, + verifyConstraints: verifyConstraints, + verifyStorage: verifyStorage, + verifyDevices: verifyDevices, + bd: bd, + machineRefCounts: make(map[string]int), + charms: charms, + } + if bd.Type != "" && bd.Type != kubernetes { + verifier.addErrorf("bundle has an invalid type %q", bd.Type) + } + if bd.Type == kubernetes { + if len(bd.Machines) > 0 { + verifier.addErrorf("bundle machines not valid for Kubernetes bundles") + } + bd.Machines = nil + } + for id := range bd.Machines { + verifier.machineRefCounts[id] = 0 + } + if bd.Series != "" && !IsValidSeries(bd.Series) { + verifier.addErrorf("bundle declares an invalid series %q", bd.Series) + } + if bd.DefaultBase != "" { + if _, err := ParseBase(bd.DefaultBase); err != nil { + verifier.addErrorf("bundle declares an invalid base %q", bd.DefaultBase) + } + } + verifier.verifySaas() + verifier.verifyMachines() + verifier.verifyApplications() + verifier.verifyRelations() + verifier.verifyOptions() + verifier.verifyEndpointBindings() + + for id, count := range verifier.machineRefCounts { + if count == 0 { + verifier.addErrorf("machine %q is not referred to by a placement directive", id) + } + } + return verifier.err() +} + +var ( + validMachineId = regexp.MustCompile("^" + names.NumberSnippet + "$") + validStorageName = regexp.MustCompile("^" + names.StorageNameSnippet + "$") + validDeviceName = regexp.MustCompile("^" + "(?:[a-z][a-z0-9]*(?:-[a-z0-9]*[a-z][a-z0-9]*)*)" + "$") + + // When the operator consumes the offer a pseudo-application with the + // offer name will be created by the controller. So using the application + // name regex makes sense here. Likewise we can use the relation regex + // to validate the endpoint name. + validOfferName = regexp.MustCompile("^" + names.ApplicationSnippet + "$") + validOfferEndpointName = regexp.MustCompile("^" + names.RelationSnippet + "$") +) + +func (verifier *bundleDataVerifier) verifySaas() { + for name, saas := range verifier.bd.Saas { + if _, ok := verifier.bd.Applications[name]; ok { + verifier.addErrorf("application %[1]q already exists with SAAS %[1]q name", name) + } + if !validOfferName.MatchString(name) { + verifier.addErrorf("invalid SAAS name %q found", name) + } + if saas == nil { + continue + } + if saas.URL != "" && !IsValidOfferURL(saas.URL) { + verifier.addErrorf("invalid offer URL %q for SAAS %s", saas.URL, name) + } + } +} + +func (verifier *bundleDataVerifier) verifyMachines() { + for id, m := range verifier.bd.Machines { + if !validMachineId.MatchString(id) { + verifier.addErrorf("invalid machine id %q found in machines", id) + } + if m == nil { + continue + } + if m.Constraints != "" { + if err := verifier.verifyConstraints(m.Constraints); err != nil { + verifier.addErrorf("invalid constraints %q in machine %q: %v", m.Constraints, id, err) + } + } + if m.Series != "" && !IsValidSeries(m.Series) { + verifier.addErrorf("invalid series %q for machine %q", m.Series, id) + } + if m.Base != "" { + if _, err := ParseBase(m.Base); err != nil { + verifier.addErrorf("invalid base %q for machine %q", m.Base, id) + } + } + } +} + +func (verifier *bundleDataVerifier) verifyApplications() { + if len(verifier.bd.Applications) == 0 { + verifier.addErrorf("at least one application must be specified") + return + } + for name, app := range verifier.bd.Applications { + if app == nil { + verifier.addErrorf("bundle application for key %q is undefined", name) + continue + } + if app.Charm == "" { + verifier.addErrorf("empty charm path") + } + if _, ok := verifier.bd.Saas[name]; ok { + verifier.addErrorf("SAAS %[1]q already exists with application %[1]q name", name) + } + // Charm may be a local directory or a charm URL. + var curl *URL + var err error + if strings.HasPrefix(app.Charm, ".") || filepath.IsAbs(app.Charm) { + charmPath := app.Charm + if !filepath.IsAbs(charmPath) { + charmPath = filepath.Join(verifier.bundleDir, charmPath) + } + if _, err := os.Stat(charmPath); err != nil { + if os.IsNotExist(err) { + verifier.addErrorf("charm path in application %q does not exist: %v", name, charmPath) + } else { + verifier.addErrorf("invalid charm path in application %q: %v", name, err) + } + } + } else if curl, err = ParseURL(app.Charm); err != nil { + verifier.addErrorf("invalid charm URL in application %q: %v", name, err) + } + + // Check the revision. + if curl != nil { + if CharmHub.Matches(curl.Schema) && curl.Revision != -1 { + verifier.addErrorf("cannot specify revision in %q, please use revision", curl.String()) + } + if app.Revision != nil { + if CharmHub.Matches(curl.Schema) && app.Channel == "" { + verifier.addErrorf("application %q with a revision requires a channel for future upgrades, please use channel", name) + } + if *app.Revision < 0 { + verifier.addErrorf("the revision for application %q must be zero or greater", name) + } + } + } + + // Check the Series. + if curl != nil && curl.Series != "" && app.Series != "" && curl.Series != app.Series { + verifier.addErrorf("the charm URL for application %q has a series which does not match, please remove the series from the URL", name) + } + if app.Series != "" && !IsValidSeries(app.Series) { + verifier.addErrorf("application %q declares an invalid series %q", name, app.Series) + } + // Check the Base + if app.Base != "" { + if _, err := ParseBase(app.Base); err != nil { + verifier.addErrorf("application %q declares an invalid base %q", name, app.Base) + } + } + // Check the Constraints. + if err := verifier.verifyConstraints(app.Constraints); err != nil { + verifier.addErrorf("invalid constraints %q in application %q: %v", app.Constraints, name, err) + } + // Check the Storage. + for storageName, storageConstraints := range app.Storage { + if !validStorageName.MatchString(storageName) { + verifier.addErrorf("invalid storage name %q in application %q", storageName, name) + } + if err := verifier.verifyStorage(storageConstraints); err != nil { + verifier.addErrorf("invalid storage %q in application %q: %v", storageName, name, err) + } + } + // Check the Devices. + for deviceName, deviceConstraints := range app.Devices { + if !validDeviceName.MatchString(deviceName) { + verifier.addErrorf("invalid device name %q in application %q", deviceName, name) + } + if err := verifier.verifyDevices(deviceConstraints); err != nil { + verifier.addErrorf("invalid device %q in application %q: %v", deviceName, name, err) + } + } + // Check the offers. + for offerName, oSpec := range app.Offers { + if !validOfferName.MatchString(offerName) { + verifier.addErrorf("invalid offer name %q in application %q", offerName, name) + } + + for _, endpoint := range oSpec.Endpoints { + if !validOfferEndpointName.MatchString(endpoint) { + verifier.addErrorf("invalid endpoint name %q for offer %q in application %q", endpoint, offerName, name) + } + } + } + if verifier.charms != nil { + if ch, ok := verifier.charms[app.Charm]; ok { + if ch.Meta().Subordinate { + if len(app.To) > 0 { + verifier.addErrorf("application %q is subordinate but specifies unit placement", name) + } + if app.NumUnits > 0 { + verifier.addErrorf("application %q is subordinate but has non-zero num_units", name) + } + } + } else { + verifier.addErrorf("application %q refers to non-existent charm %q", name, app.Charm) + } + } + for resName, rev := range app.Resources { + if resName == "" { + verifier.addErrorf("missing resource name on application %q", name) + } + switch rev.(type) { + case int, string: + default: + verifier.addErrorf("resource revision %q is not int or string", name) + } + } + if app.NumUnits < 0 { + verifier.addErrorf("negative number of units specified on application %q", name) + } + if verifier.bd.Type == kubernetes { + verifier.verifyKubernetesPlacement(name, app.To) + } else { + verifier.verifyPlacement(name, app.NumUnits, app.To) + } + + // Check expose parameters. We do not allow both the expose and + // the exposed-endpoints fields to be specified at the same + // time. Otherwise, an operator might export a 2.9 bundle + // containing an exposed application with endpoint-specific + // rules and them import it into a 2.8 controller which is not + // aware of this field causing the application to be exposed + // to 0.0.0.0/0! + if len(app.ExposedEndpoints) != 0 { + if app.Expose { + verifier.addErrorf(`exposed-endpoints cannot be specified together with "exposed:true" in application %q as this poses a security risk when deploying bundles to older controllers`, name) + } else { + for epName, expDetails := range app.ExposedEndpoints { + for _, cidr := range expDetails.ExposeToCIDRs { + if _, _, err := net.ParseCIDR(cidr); err != nil { + verifier.addErrorf("invalid CIDR %q for expose to CIDRs field for endpoint %q in application %q", cidr, epName, name) + } + } + } + } + } + } +} + +func (verifier *bundleDataVerifier) verifyPlacement(name string, numUnits int, to []string) { + if numUnits >= 0 && len(to) > numUnits { + verifier.addErrorf("too many units specified in unit placement for application %q", name) + } + for _, p := range to { + up, err := ParsePlacement(p) + if err != nil { + verifier.addError(err) + continue + } + switch { + case up.Application != "": + spec, ok := verifier.bd.Applications[up.Application] + if !ok { + verifier.addErrorf("placement %q refers to an application not defined in this bundle", p) + continue + } + if up.Unit >= 0 && up.Unit >= spec.NumUnits { + verifier.addErrorf("placement %q specifies a unit greater than the %d unit(s) started by the target application", p, spec.NumUnits) + } + case up.Machine == "new": + default: + _, ok := verifier.bd.Machines[up.Machine] + if !ok { + verifier.addErrorf("placement %q refers to a machine not defined in this bundle", p) + continue + } + verifier.machineRefCounts[up.Machine]++ + } + } +} + +func (verifier *bundleDataVerifier) verifyKubernetesPlacement(name string, to []string) { + if len(to) > 1 { + verifier.addErrorf("too many placement directives for application %q", name) + return + } + if len(to) == 0 { + return + } + _, err := keyvalues.Parse(strings.Split(to[0], ","), false) + if err != nil { + verifier.addErrorf("%v for application %q", err, name) + } +} + +func (verifier *bundleDataVerifier) getCharmMetaForApplication(appName string) (*Meta, error) { + svc, ok := verifier.bd.Applications[appName] + if !ok { + return nil, fmt.Errorf("application %q not found", appName) + } + ch, ok := verifier.charms[svc.Charm] + if !ok { + return nil, fmt.Errorf("charm %q from application %q not found", svc.Charm, appName) + } + return ch.Meta(), nil +} + +func (verifier *bundleDataVerifier) verifyRelations() { + seen := make(map[[2]endpoint]bool) + for _, relPair := range verifier.bd.Relations { + if len(relPair) != 2 { + verifier.addErrorf("relation %q has %d endpoint(s), not 2", relPair, len(relPair)) + continue + } + var epPair [2]endpoint + relParseErr := false + for i, svcRel := range relPair { + ep, err := parseEndpoint(svcRel) + if err != nil { + verifier.addError(err) + relParseErr = true + continue + } + // with the introduction of the SAAS block to bundles, we should + // test that not only is the expected application is in the + // applications block, but if it's not, is it in the SAAS offering. + _, foundApp := verifier.bd.Applications[ep.application] + _, foundSaas := verifier.bd.Saas[ep.application] + if !foundApp && !foundSaas { + verifier.addErrorf("relation %q refers to application %q not defined in this bundle", relPair, ep.application) + } + if foundApp && foundSaas { + verifier.addErrorf("ambiguous relation %q refers to a application and a SAAS in this bundle", ep.application) + } + epPair[i] = ep + } + if relParseErr { + // We failed to parse at least one relation, so don't + // bother checking further. + continue + } + if epPair[0].application == epPair[1].application { + verifier.addErrorf("relation %q relates an application to itself", relPair) + } + // Resolve endpoint relations if necessary and we have + // the necessary charm information. + if (epPair[0].relation == "" || epPair[1].relation == "") && verifier.charms != nil { + iep0, iep1, err := inferEndpoints(epPair[0], epPair[1], verifier.getCharmMetaForApplication) + if err != nil { + verifier.addErrorf("cannot infer endpoint between %s and %s: %v", epPair[0], epPair[1], err) + } else { + // Change the endpoints that get recorded + // as seen, so we'll diagnose a duplicate + // relation even if one relation specifies + // the relations explicitly and the other does + // not. + epPair[0], epPair[1] = iep0, iep1 + } + } + + // Re-order pairs so that we diagnose duplicate relations + // whichever way they're specified. + if epPair[1].less(epPair[0]) { + epPair[1], epPair[0] = epPair[0], epPair[1] + } + if _, ok := seen[epPair]; ok { + verifier.addErrorf("relation %q is defined more than once", relPair) + } + if verifier.charms != nil && epPair[0].relation != "" && epPair[1].relation != "" { + // We have charms to verify against, and the + // endpoint has been fully specified or inferred. + verifier.verifyRelation(epPair[0], epPair[1]) + } + seen[epPair] = true + } +} + +func (verifier *bundleDataVerifier) verifyEndpointBindings() { + for name, svc := range verifier.bd.Applications { + if svc == nil { + continue + } + + // Verify the endpoint bindings from the fully qualified charm URL and + // not just the application name. Fallback to the charm name as the + // application name, but in reality this shouldn't be the case. + var ( + charm Charm + ok bool + ) + if charm, ok = verifier.charms[svc.Charm]; !ok { + if charm, ok = verifier.charms[name]; !ok { + continue + } + } + for endpoint, space := range svc.EndpointBindings { + _, isInProvides := charm.Meta().Provides[endpoint] + _, isInRequires := charm.Meta().Requires[endpoint] + _, isInPeers := charm.Meta().Peers[endpoint] + _, isInExtraBindings := charm.Meta().ExtraBindings[endpoint] + + if !(isInProvides || isInRequires || isInPeers || isInExtraBindings) { + verifier.addErrorf( + "application %q wants to bind endpoint %q to space %q, "+ + "but the endpoint is not defined by the charm", + name, endpoint, space) + } + } + + } +} + +var infoRelation = Relation{ + Name: "juju-info", + Role: RoleProvider, + Interface: "juju-info", + Scope: ScopeContainer, +} + +// verifyRelation verifies a single relation. +// It checks that both endpoints of the relation are +// defined, and that the relationship is correctly +// symmetrical (provider to requirer) and shares +// the same interface. +func (verifier *bundleDataVerifier) verifyRelation(ep0, ep1 endpoint) { + svc0 := verifier.bd.Applications[ep0.application] + svc1 := verifier.bd.Applications[ep1.application] + if svc0 == nil || svc1 == nil || svc0 == svc1 { + // An error will be produced by verifyRelations for this case. + return + } + charm0 := verifier.charms[svc0.Charm] + charm1 := verifier.charms[svc1.Charm] + if charm0 == nil || charm1 == nil { + // An error will be produced by verifyApplications for this case. + return + } + relProv0, okProv0 := charm0.Meta().Provides[ep0.relation] + // The juju-info relation is provided implicitly by every + // charm - use it if required. + if !okProv0 && ep0.relation == infoRelation.Name { + relProv0, okProv0 = infoRelation, true + } + relReq0, okReq0 := charm0.Meta().Requires[ep0.relation] + if !okProv0 && !okReq0 { + verifier.addErrorf("charm %q used by application %q does not define relation %q", svc0.Charm, ep0.application, ep0.relation) + } + relProv1, okProv1 := charm1.Meta().Provides[ep1.relation] + // The juju-info relation is provided implicitly by every + // charm - use it if required. + if !okProv1 && ep1.relation == infoRelation.Name { + relProv1, okProv1 = infoRelation, true + } + relReq1, okReq1 := charm1.Meta().Requires[ep1.relation] + if !okProv1 && !okReq1 { + verifier.addErrorf("charm %q used by application %q does not define relation %q", svc1.Charm, ep1.application, ep1.relation) + } + + var relProv, relReq Relation + var epProv, epReq endpoint + switch { + case okProv0 && okReq1: + relProv, relReq = relProv0, relReq1 + epProv, epReq = ep0, ep1 + case okReq0 && okProv1: + relProv, relReq = relProv1, relReq0 + epProv, epReq = ep1, ep0 + case okProv0 && okProv1: + verifier.addErrorf("relation %q to %q relates provider to provider", ep0, ep1) + return + case okReq0 && okReq1: + verifier.addErrorf("relation %q to %q relates requirer to requirer", ep0, ep1) + return + default: + // Errors were added above. + return + } + if relProv.Interface != relReq.Interface { + verifier.addErrorf("mismatched interface between %q and %q (%q vs %q)", epProv, epReq, relProv.Interface, relReq.Interface) + } +} + +// verifyOptions verifies that the options are correctly defined +// with respect to the charm config options. +func (verifier *bundleDataVerifier) verifyOptions() { + if verifier.charms == nil { + return + } + for appName, svc := range verifier.bd.Applications { + charm := verifier.charms[svc.Charm] + if charm == nil { + // An error will be produced by verifyApplications for this case. + continue + } + config := charm.Config() + for name, value := range svc.Options { + opt, ok := config.Options[name] + if !ok { + verifier.addErrorf("cannot validate application %q: configuration option %q not found in charm %q", appName, name, svc.Charm) + continue + } + _, err := opt.validate(name, value) + if err != nil { + verifier.addErrorf("cannot validate application %q: %v", appName, err) + } + } + } +} + +var validApplicationRelation = regexp.MustCompile("^(" + names.ApplicationSnippet + "):(" + names.RelationSnippet + ")$") + +type endpoint struct { + application string + relation string +} + +func (ep endpoint) String() string { + if ep.relation == "" { + return ep.application + } + return fmt.Sprintf("%s:%s", ep.application, ep.relation) +} + +func (ep endpoint) less(other endpoint) bool { + if ep.application == other.application { + return ep.relation < other.relation + } + return ep.application < other.application +} + +func parseEndpoint(ep string) (endpoint, error) { + m := validApplicationRelation.FindStringSubmatch(ep) + if m != nil { + return endpoint{ + application: m[1], + relation: m[2], + }, nil + } + if !names.IsValidApplication(ep) { + return endpoint{}, fmt.Errorf("invalid relation syntax %q", ep) + } + return endpoint{ + application: ep, + }, nil +} + +// endpointInfo holds information about one endpoint of a relation. +type endpointInfo struct { + applicationName string + Relation +} + +// String returns the unique identifier of the relation endpoint. +func (ep endpointInfo) String() string { + return ep.applicationName + ":" + ep.Name +} + +// canRelateTo returns whether a relation may be established between ep +// and other. +func (ep endpointInfo) canRelateTo(other endpointInfo) bool { + return ep.applicationName != other.applicationName && + ep.Interface == other.Interface && + ep.Role != RolePeer && + counterpartRole(ep.Role) == other.Role +} + +// endpoint returns the endpoint specifier for ep. +func (ep endpointInfo) endpoint() endpoint { + return endpoint{ + application: ep.applicationName, + relation: ep.Name, + } +} + +// counterpartRole returns the RelationRole that the given RelationRole +// can relate to. +func counterpartRole(r RelationRole) RelationRole { + switch r { + case RoleProvider: + return RoleRequirer + case RoleRequirer: + return RoleProvider + case RolePeer: + return RolePeer + } + panic(fmt.Errorf("unknown relation role %q", r)) +} + +type UnitPlacement struct { + // ContainerType holds the container type of the new + // new unit, or empty if unspecified. + ContainerType string + + // Machine holds the numeric machine id, or "new", + // or empty if the placement specifies an application. + Machine string + + // application holds the application name, or empty if + // the placement specifies a machine. + Application string + + // Unit holds the unit number of the application, or -1 + // if unspecified. + Unit int +} + +var snippetReplacer = strings.NewReplacer( + "container", names.ContainerTypeSnippet, + "number", names.NumberSnippet, + "application", names.ApplicationSnippet, +) + +// validPlacement holds regexp that matches valid placement requests. To +// make the expression easier to comprehend and maintain, we replace +// symbolic snippet references in the regexp by their actual regexps +// using snippetReplacer. +var validPlacement = regexp.MustCompile( + snippetReplacer.Replace( + "^(?:(container):)?(?:(application)(?:/(number))?|(number))$", + ), +) + +// ParsePlacement parses a unit placement directive, as +// specified in the To clause of an application entry in the +// applications section of a bundle. +func ParsePlacement(p string) (*UnitPlacement, error) { + m := validPlacement.FindStringSubmatch(p) + if m == nil { + return nil, fmt.Errorf("invalid placement syntax %q", p) + } + up := UnitPlacement{ + ContainerType: m[1], + Application: m[2], + Machine: m[4], + } + if unitStr := m[3]; unitStr != "" { + // We know that unitStr must be a valid integer because + // it's specified as such in the regexp. + up.Unit, _ = strconv.Atoi(unitStr) + } else { + up.Unit = -1 + } + if up.Application == "new" { + if up.Unit != -1 { + return nil, fmt.Errorf("invalid placement syntax %q", p) + } + up.Machine, up.Application = "new", "" + } + return &up, nil +} + +// inferEndpoints infers missing relation names from the given endpoint +// specifications, using the given get function to retrieve charm +// data if necessary. It returns the fully specified endpoints. +func inferEndpoints(epSpec0, epSpec1 endpoint, get func(svc string) (*Meta, error)) (endpoint, endpoint, error) { + if epSpec0.relation != "" && epSpec1.relation != "" { + // The endpoints are already specified explicitly so + // there is no need to fetch any charm data to infer + // them. + return epSpec0, epSpec1, nil + } + eps0, err := possibleEndpoints(epSpec0, get) + if err != nil { + return endpoint{}, endpoint{}, err + } + eps1, err := possibleEndpoints(epSpec1, get) + if err != nil { + return endpoint{}, endpoint{}, err + } + var candidates [][]endpointInfo + for _, ep0 := range eps0 { + for _, ep1 := range eps1 { + if ep0.canRelateTo(ep1) { + candidates = append(candidates, []endpointInfo{ep0, ep1}) + } + } + } + switch len(candidates) { + case 0: + return endpoint{}, endpoint{}, fmt.Errorf("no relations found") + case 1: + return candidates[0][0].endpoint(), candidates[0][1].endpoint(), nil + } + + // There's ambiguity; try discarding implicit relations. + filtered := discardImplicitRelations(candidates) + if len(filtered) == 1 { + return filtered[0][0].endpoint(), filtered[0][1].endpoint(), nil + } + // The ambiguity cannot be resolved, so return an error. + var keys []string + for _, cand := range candidates { + keys = append(keys, fmt.Sprintf("%q", relationKey(cand))) + } + sort.Strings(keys) + return endpoint{}, endpoint{}, fmt.Errorf("ambiguous relation: %s %s could refer to %s", + epSpec0, epSpec1, strings.Join(keys, "; ")) +} + +func discardImplicitRelations(candidates [][]endpointInfo) [][]endpointInfo { + var filtered [][]endpointInfo +outer: + for _, cand := range candidates { + for _, ep := range cand { + if ep.IsImplicit() { + continue outer + } + } + filtered = append(filtered, cand) + } + return filtered +} + +// relationKey returns a string describing the relation defined by +// endpoints, for use in various contexts (including error messages). +func relationKey(endpoints []endpointInfo) string { + var names []string + for _, ep := range endpoints { + names = append(names, ep.String()) + } + sort.Strings(names) + return strings.Join(names, " ") +} + +// possibleEndpoints returns all the endpoints that the given endpoint spec +// could refer to. +func possibleEndpoints(epSpec endpoint, get func(svc string) (*Meta, error)) ([]endpointInfo, error) { + meta, err := get(epSpec.application) + if err != nil { + return nil, err + } + + var eps []endpointInfo + add := func(r Relation) { + if epSpec.relation == "" || epSpec.relation == r.Name { + eps = append(eps, endpointInfo{ + applicationName: epSpec.application, + Relation: r, + }) + } + } + + for _, r := range meta.Provides { + add(r) + } + for _, r := range meta.Requires { + add(r) + } + // Every application implicitly provides a juju-info relation. + add(Relation{ + Name: "juju-info", + Role: RoleProvider, + Interface: "juju-info", + Scope: ScopeGlobal, + }) + return eps, nil +} diff --git a/internal/charm/bundledata_test.go b/internal/charm/bundledata_test.go new file mode 100644 index 00000000000..3cfb6db825c --- /dev/null +++ b/internal/charm/bundledata_test.go @@ -0,0 +1,1559 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/juju/mgo/v3/bson" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +type bundleDataSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&bundleDataSuite{}) + +const mediawikiBundle = ` +default-base: ubuntu@20.04 +applications: + mediawiki: + charm: "mediawiki" + num_units: 1 + expose: true + options: + debug: false + name: Please set name of wiki + skin: vector + annotations: + "gui-x": 609 + "gui-y": -15 + storage: + valid-store: 10G + bindings: + db: db + website: public + resources: + data: 3 + mysql: + charm: "mysql" + num_units: 2 + to: [0, mediawiki/0] + base: ubuntu@22.04 + options: + "binlog-format": MIXED + "block-size": 5.3 + "dataset-size": "80%" + flavor: distro + "ha-bindiface": eth0 + "ha-mcastport": 5411.1 + annotations: + "gui-x": 610 + "gui-y": 255 + constraints: "mem=8g" + bindings: + db: db + resources: + data: "resources/data.tar" +relations: + - ["mediawiki:db", "mysql:db"] + - ["mysql:foo", "mediawiki:bar"] +machines: + 0: + constraints: 'arch=amd64 mem=4g' + annotations: + foo: bar +tags: + - super + - awesome +description: | + Everything is awesome. Everything is cool when we work as a team. + Lovely day. +` + +// Revision are an *int, create a few ints for their addresses used in tests. +var ( + five = 5 + ten = 10 + twentyEight = 28 +) + +var parseTests = []struct { + about string + data string + expectedBD *charm.BundleData + expectedErr string +}{{ + about: "mediawiki", + data: mediawikiBundle, + expectedBD: &charm.BundleData{ + DefaultBase: "ubuntu@20.04", + Applications: map[string]*charm.ApplicationSpec{ + "mediawiki": { + Charm: "mediawiki", + NumUnits: 1, + Expose: true, + Options: map[string]interface{}{ + "debug": false, + "name": "Please set name of wiki", + "skin": "vector", + }, + Annotations: map[string]string{ + "gui-x": "609", + "gui-y": "-15", + }, + Storage: map[string]string{ + "valid-store": "10G", + }, + EndpointBindings: map[string]string{ + "db": "db", + "website": "public", + }, + Resources: map[string]interface{}{ + "data": 3, + }, + }, + "mysql": { + Charm: "mysql", + NumUnits: 2, + To: []string{"0", "mediawiki/0"}, + Base: "ubuntu@22.04", + Options: map[string]interface{}{ + "binlog-format": "MIXED", + "block-size": 5.3, + "dataset-size": "80%", + "flavor": "distro", + "ha-bindiface": "eth0", + "ha-mcastport": 5411.1, + }, + Annotations: map[string]string{ + "gui-x": "610", + "gui-y": "255", + }, + Constraints: "mem=8g", + EndpointBindings: map[string]string{ + "db": "db", + }, + Resources: map[string]interface{}{"data": "resources/data.tar"}, + }, + }, + Machines: map[string]*charm.MachineSpec{ + "0": { + Constraints: "arch=amd64 mem=4g", + Annotations: map[string]string{ + "foo": "bar", + }, + }, + }, + Relations: [][]string{ + {"mediawiki:db", "mysql:db"}, + {"mysql:foo", "mediawiki:bar"}, + }, + Tags: []string{"super", "awesome"}, + Description: `Everything is awesome. Everything is cool when we work as a team. +Lovely day. +`, + }, +}, { + about: "relations specified with hyphens", + data: ` +relations: + - - "mediawiki:db" + - "mysql:db" + - - "mysql:foo" + - "mediawiki:bar" +`, + expectedBD: &charm.BundleData{ + Relations: [][]string{ + {"mediawiki:db", "mysql:db"}, + {"mysql:foo", "mediawiki:bar"}, + }, + }, +}, { + about: "scale alias for num_units", + data: ` +applications: + mysql: + charm: mysql + scale: 1 +`, + expectedBD: &charm.BundleData{ + Applications: map[string]*charm.ApplicationSpec{ + "mysql": { + Charm: "mysql", + NumUnits: 1, + }, + }, + }, +}, { + about: "application requiring explicit trust", + data: ` +applications: + aws-integrator: + charm: aws-integrator + num_units: 1 + trust: true +`, + expectedBD: &charm.BundleData{ + Applications: map[string]*charm.ApplicationSpec{ + "aws-integrator": { + Charm: "aws-integrator", + NumUnits: 1, + RequiresTrust: true, + }, + }, + }, +}, { + about: "application defining offers", + data: ` +applications: + apache2: + charm: "apache2" + revision: 28 + num_units: 1 + offers: + offer1: + endpoints: + - "apache-website" + - "apache-proxy" + acl: + admin: "admin" + foo: "consume" + offer2: + endpoints: + - "apache-website" +`, + expectedBD: &charm.BundleData{ + Applications: map[string]*charm.ApplicationSpec{ + "apache2": { + Charm: "apache2", + Revision: &twentyEight, + NumUnits: 1, + Offers: map[string]*charm.OfferSpec{ + "offer1": { + Endpoints: []string{ + "apache-website", + "apache-proxy", + }, + ACL: map[string]string{ + "admin": "admin", + "foo": "consume", + }, + }, + "offer2": { + Endpoints: []string{ + "apache-website", + }, + }, + }, + }, + }, + }, +}, { + about: "saas offerings", + data: ` +saas: + apache2: + url: production:admin/info.apache +applications: + apache2: + charm: "apache2" + revision: 10 + num_units: 1 +`, + expectedBD: &charm.BundleData{ + Saas: map[string]*charm.SaasSpec{ + "apache2": { + URL: "production:admin/info.apache", + }, + }, + Applications: map[string]*charm.ApplicationSpec{ + "apache2": { + Charm: "apache2", + Revision: &ten, + NumUnits: 1, + }, + }, + }, +}, { + about: "saas offerings with relations", + data: ` +saas: + mysql: + url: production:admin/info.mysql +applications: + wordpress: + charm: "ch:wordpress" + series: "trusty" + revision: 10 + num_units: 1 +relations: +- - wordpress:db + - mysql:db +`, + expectedBD: &charm.BundleData{ + Saas: map[string]*charm.SaasSpec{ + "mysql": { + URL: "production:admin/info.mysql", + }, + }, + Applications: map[string]*charm.ApplicationSpec{ + "wordpress": { + Charm: "ch:wordpress", + Series: "trusty", + Revision: &ten, + NumUnits: 1, + }, + }, + Relations: [][]string{ + {"wordpress:db", "mysql:db"}, + }, + }, +}, { + about: "charm channel", + data: ` +applications: + wordpress: + charm: "wordpress" + revision: 10 + series: trusty + channel: edge + num_units: 1 +`, + expectedBD: &charm.BundleData{ + Applications: map[string]*charm.ApplicationSpec{ + "wordpress": { + Charm: "wordpress", + Channel: "edge", + Revision: &ten, + NumUnits: 1, + Series: "trusty", + }, + }, + }, +}, { + about: "charm revision and channel", + data: ` +applications: + wordpress: + charm: "wordpress" + revision: 5 + channel: edge + num_units: 1 +`, + expectedBD: &charm.BundleData{ + Applications: map[string]*charm.ApplicationSpec{ + "wordpress": { + Charm: "wordpress", + Revision: &five, + Channel: "edge", + NumUnits: 1, + }, + }, + }, +}} + +func (*bundleDataSuite) TestParse(c *gc.C) { + for i, test := range parseTests { + c.Logf("test %d: %s", i, test.about) + bd, err := charm.ReadBundleData(strings.NewReader(test.data)) + if test.expectedErr != "" { + c.Assert(err, gc.ErrorMatches, test.expectedErr) + continue + } + c.Assert(err, gc.IsNil) + c.Assert(bd, jc.DeepEquals, test.expectedBD) + } +} + +func (*bundleDataSuite) TestCodecRoundTrip(c *gc.C) { + for i, test := range parseTests { + if test.expectedErr != "" { + continue + } + // Check that for all the known codecs, we can + // round-trip the bundle data through them. + for _, codec := range codecs { + + c.Logf("Code Test %s for test %d: %s", codec.Name, i, test.about) + + data, err := codec.Marshal(test.expectedBD) + c.Assert(err, gc.IsNil) + var bd charm.BundleData + err = codec.Unmarshal(data, &bd) + c.Assert(err, gc.IsNil) + + for _, app := range bd.Applications { + for resName, res := range app.Resources { + if val, ok := res.(float64); ok { + app.Resources[resName] = int(val) + } + } + } + + c.Assert(&bd, jc.DeepEquals, test.expectedBD) + } + } +} + +func (*bundleDataSuite) TestParseLocalWithSeries(c *gc.C) { + path := "internal/test-charm-repo/quanta/riak" + data := fmt.Sprintf(` + applications: + dummy: + charm: %s + series: xenial + num_units: 1 + `, path) + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + c.Assert(bd, jc.DeepEquals, &charm.BundleData{ + Applications: map[string]*charm.ApplicationSpec{ + "dummy": { + Charm: path, + Series: "xenial", + NumUnits: 1, + }, + }}) +} + +func (s *bundleDataSuite) TestBSONNilData(c *gc.C) { + bd := map[string]*charm.BundleData{ + "test": nil, + } + data, err := bson.Marshal(bd) + c.Assert(err, jc.ErrorIsNil) + var result map[string]*charm.BundleData + err = bson.Unmarshal(data, &result) + c.Assert(err, gc.IsNil) + c.Assert(result["test"], gc.IsNil) +} + +var verifyErrorsTests = []struct { + about string + data string + errors []string +}{{ + about: "as many errors as possible", + data: ` +series: "9wrong" +default-base: "invalidbase" + +saas: + apache2: + url: '!some-bogus/url' + riak: + url: production:admin/info.riak +machines: + 0: + constraints: 'bad constraints' + annotations: + foo: bar + series: 'bad series' + base: 'bad base' + bogus: + 3: +applications: + mediawiki: + charm: "bogus:precise/mediawiki-10" + num_units: -4 + options: + debug: false + name: Please set name of wiki + skin: vector + annotations: + "gui-x": 609 + "gui-y": -15 + resources: + "": 42 + "foo": + "not": int + riak: + charm: "./somepath" + mysql: + charm: "mysql" + num_units: 2 + to: [0, mediawiki/0, nowhere/3, 2, "bad placement"] + options: + "binlog-format": MIXED + "block-size": 5 + "dataset-size": "80%" + flavor: distro + "ha-bindiface": eth0 + "ha-mcastport": 5411 + annotations: + "gui-x": 610 + "gui-y": 255 + constraints: "bad constraints" + wordpress: + charm: wordpress + postgres: + charm: "postgres" + series: trusty + terracotta: + charm: "terracotta" + base: "ubuntu@22.04" + ceph: + charm: ceph + storage: + valid-storage: 3,10G + no_underscores: 123 + ceph-osd: + charm: ceph-osd + storage: + invalid-storage: "bad storage constraints" +relations: + - ["mediawiki:db", "mysql:db"] + - ["mysql:foo", "mediawiki:bar"] + - ["arble:bar"] + - ["arble:bar", "mediawiki:db"] + - ["mysql:foo", "mysql:bar"] + - ["mysql:db", "mediawiki:db"] + - ["mediawiki/db", "mysql:db"] + - ["wordpress", "mysql"] + - ["wordpress:db", "riak:db"] +`, + errors: []string{ + `bundle declares an invalid series "9wrong"`, + `bundle declares an invalid base "invalidbase"`, + `invalid offer URL "!some-bogus/url" for SAAS apache2`, + `invalid storage name "no_underscores" in application "ceph"`, + `invalid storage "invalid-storage" in application "ceph-osd": bad storage constraint`, + `machine "3" is not referred to by a placement directive`, + `machine "bogus" is not referred to by a placement directive`, + `invalid machine id "bogus" found in machines`, + `invalid constraints "bad constraints" in machine "0": bad constraint`, + `invalid charm URL in application "mediawiki": cannot parse URL "bogus:precise/mediawiki-10": schema "bogus" not valid`, + `charm path in application "riak" does not exist: internal/test-charm-repo/bundle/somepath`, + `invalid constraints "bad constraints" in application "mysql": bad constraint`, + `negative number of units specified on application "mediawiki"`, + `missing resource name on application "mediawiki"`, + `resource revision "mediawiki" is not int or string`, + `too many units specified in unit placement for application "mysql"`, + `placement "nowhere/3" refers to an application not defined in this bundle`, + `placement "mediawiki/0" specifies a unit greater than the -4 unit(s) started by the target application`, + `placement "2" refers to a machine not defined in this bundle`, + `relation ["arble:bar"] has 1 endpoint(s), not 2`, + `relation ["arble:bar" "mediawiki:db"] refers to application "arble" not defined in this bundle`, + `relation ["mysql:foo" "mysql:bar"] relates an application to itself`, + `relation ["mysql:db" "mediawiki:db"] is defined more than once`, + `invalid placement syntax "bad placement"`, + `invalid relation syntax "mediawiki/db"`, + `invalid series "bad series" for machine "0"`, + `invalid base "bad base" for machine "0"`, + `ambiguous relation "riak" refers to a application and a SAAS in this bundle`, + `SAAS "riak" already exists with application "riak" name`, + `application "riak" already exists with SAAS "riak" name`, + }, +}, { + about: "mediawiki should be ok", + data: mediawikiBundle, +}, { + about: "malformed offer and endpoint names", + data: ` +applications: + aws-integrator: + charm: aws-integrator + num_units: 1 + trust: true + offers: + $bad-name: + endpoints: + - "nope!" +`, + errors: []string{ + `invalid offer name "$bad-name" in application "aws-integrator"`, + `invalid endpoint name "nope!" for offer "$bad-name" in application "aws-integrator"`, + }, +}, { + about: "expose parameters provided together with expose:true", + data: ` +applications: + aws-integrator: + charm: "aws-integrator" + expose: true + exposed-endpoints: + admin: + expose-to-spaces: + - alpha + expose-to-cidrs: + - 13.37.0.0/16 + num_units: 1 +`, + errors: []string{ + `exposed-endpoints cannot be specified together with "exposed:true" in application "aws-integrator" as this poses a security risk when deploying bundles to older controllers`, + }, +}, { + about: "invalid CIDR in expose-to-cidrs parameter when the app is exposed", + data: ` +applications: + aws-integrator: + charm: "aws-integrator" + exposed-endpoints: + admin: + expose-to-spaces: + - alpha + expose-to-cidrs: + - not-a-cidr + num_units: 1 +`, + errors: []string{ + `invalid CIDR "not-a-cidr" for expose to CIDRs field for endpoint "admin" in application "aws-integrator"`, + }, +}} + +func (*bundleDataSuite) TestVerifyErrors(c *gc.C) { + for i, test := range verifyErrorsTests { + c.Logf("test %d: %s", i, test.about) + assertVerifyErrors(c, test.data, nil, test.errors) + } +} + +func assertVerifyErrors(c *gc.C, bundleData string, charms map[string]charm.Charm, expectErrors []string) { + bd, err := charm.ReadBundleData(strings.NewReader(bundleData)) + c.Assert(err, gc.IsNil) + + validateConstraints := func(c string) error { + if c == "bad constraints" { + return fmt.Errorf("bad constraint") + } + return nil + } + validateStorage := func(c string) error { + if c == "bad storage constraints" { + return fmt.Errorf("bad storage constraint") + } + return nil + } + validateDevices := func(c string) error { + if c == "bad device constraints" { + return fmt.Errorf("bad device constraint") + } + return nil + } + if charms != nil { + err = bd.VerifyWithCharms(validateConstraints, validateStorage, validateDevices, charms) + } else { + err = bd.VerifyLocal("internal/test-charm-repo/bundle", validateConstraints, validateStorage, validateDevices) + } + + if len(expectErrors) == 0 { + if err == nil { + return + } + // Let the rest of the function deal with the + // error, so that we'll see the actual errors + // that resulted. + } + c.Assert(err, gc.FitsTypeOf, (*charm.VerificationError)(nil)) + errors := err.(*charm.VerificationError).Errors + errStrings := make([]string, len(errors)) + for i, err := range errors { + errStrings[i] = err.Error() + } + sort.Strings(errStrings) + sort.Strings(expectErrors) + c.Assert(errStrings, jc.DeepEquals, expectErrors) +} + +func (*bundleDataSuite) TestVerifyCharmURL(c *gc.C) { + bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) + c.Assert(err, gc.IsNil) + for i, u := range []string{ + "ch:wordpress", + "local:foo", + "local:foo-45", + } { + c.Logf("test %d: %s", i, u) + bd.Applications["mediawiki"].Charm = u + err := bd.Verify(nil, nil, nil) + c.Check(err, gc.IsNil, gc.Commentf("charm url %q", u)) + } +} + +func (*bundleDataSuite) TestVerifyLocalCharm(c *gc.C) { + bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) + c.Assert(err, gc.IsNil) + bundleDir := c.MkDir() + relativeCharmDir := filepath.Join(bundleDir, "charm") + err = os.MkdirAll(relativeCharmDir, 0700) + c.Assert(err, jc.ErrorIsNil) + for i, u := range []string{ + "ch:wordpress", + "local:foo", + "local:foo-45", + c.MkDir(), + "./charm", + } { + c.Logf("test %d: %s", i, u) + bd.Applications["mediawiki"].Charm = u + err := bd.VerifyLocal(bundleDir, nil, nil, nil) + c.Check(err, gc.IsNil, gc.Commentf("charm url %q", u)) + } +} + +func (s *bundleDataSuite) TestVerifyBundleUsingJujuInfoRelation(c *gc.C) { + err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, nil) + c.Assert(err, gc.IsNil) +} + +func (s *bundleDataSuite) testPrepareAndMutateBeforeVerifyWithCharms(c *gc.C, mutator func(bd *charm.BundleData)) error { + b := readBundleDir(c, "wordpress-with-logging") + bd := b.Data() + + charms := map[string]charm.Charm{ + "ch:wordpress": readCharmDir(c, "wordpress"), + "ch:mysql": readCharmDir(c, "mysql"), + "logging": readCharmDir(c, "logging"), + } + + if mutator != nil { + mutator(bd) + } + + return bd.VerifyWithCharms(nil, nil, nil, charms) +} + +func (s *bundleDataSuite) TestVerifyBundleWithUnknownEndpointBindingGiven(c *gc.C) { + err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { + bd.Applications["wordpress"].EndpointBindings["foo"] = "bar" + }) + c.Assert(err, gc.ErrorMatches, + `application "wordpress" wants to bind endpoint "foo" to space "bar", `+ + `but the endpoint is not defined by the charm`, + ) +} + +func (s *bundleDataSuite) TestVerifyBundleWithExtraBindingsSuccess(c *gc.C) { + err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { + // Both of these are specified in extra-bindings. + bd.Applications["wordpress"].EndpointBindings["admin-api"] = "internal" + bd.Applications["wordpress"].EndpointBindings["foo-bar"] = "test" + }) + c.Assert(err, gc.IsNil) +} + +func (s *bundleDataSuite) TestVerifyBundleWithRelationNameBindingSuccess(c *gc.C) { + err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { + // Both of these are specified in as relations. + bd.Applications["wordpress"].EndpointBindings["cache"] = "foo" + bd.Applications["wordpress"].EndpointBindings["monitoring-port"] = "bar" + }) + c.Assert(err, gc.IsNil) +} + +func (s *bundleDataSuite) TestParseKubernetesBundleType(c *gc.C) { + data := ` +bundle: kubernetes + +applications: + mariadb: + charm: "mariadb-k8s" + scale: 2 + placement: foo=bar + gitlab: + charm: "gitlab-k8s" + num_units: 3 + to: [foo=baz] + series: kubernetes + redis: + charm: "redis-k8s" + scale: 3 + to: [foo=baz] +` + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + err = bd.Verify(nil, nil, nil) + c.Assert(err, jc.ErrorIsNil) + c.Assert(bd, jc.DeepEquals, &charm.BundleData{ + Type: "kubernetes", + Applications: map[string]*charm.ApplicationSpec{ + "mariadb": { + Charm: "mariadb-k8s", + To: []string{"foo=bar"}, + NumUnits: 2, + }, + "gitlab": { + Charm: "gitlab-k8s", + Series: "kubernetes", + To: []string{"foo=baz"}, + NumUnits: 3, + }, + "redis": { + Charm: "redis-k8s", + To: []string{"foo=baz"}, + NumUnits: 3, + }}, + }) +} + +func (s *bundleDataSuite) TestInvalidBundleType(c *gc.C) { + data := ` +bundle: foo + +applications: + mariadb: + charm: mariadb-k8s + scale: 2 +` + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + err = bd.Verify(nil, nil, nil) + c.Assert(err, gc.ErrorMatches, `bundle has an invalid type "foo"`) +} + +func (s *bundleDataSuite) TestInvalidScaleAndNumUnits(c *gc.C) { + data := ` +bundle: kubernetes + +applications: + mariadb: + charm: "mariadb-k8s" + scale: 2 + num_units: 2 +` + _, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.ErrorMatches, `.*cannot specify both scale and num_units for application "mariadb"`) +} + +func (s *bundleDataSuite) TestInvalidPlacementAndTo(c *gc.C) { + data := ` +bundle: kubernetes + +applications: + mariadb: + charm: "mariadb-k8s" + placement: foo=bar + to: [foo=bar] +` + _, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.ErrorMatches, `.*cannot specify both placement and to for application "mariadb"`) +} + +func (s *bundleDataSuite) TestInvalidIAASPlacement(c *gc.C) { + data := ` +applications: + mariadb: + charm: "mariadb" + placement: foo=bar +` + _, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.ErrorMatches, `.*placement \(foo=bar\) not valid for non-Kubernetes application "mariadb"`) +} + +func (s *bundleDataSuite) TestKubernetesBundleErrors(c *gc.C) { + data := ` +bundle: "kubernetes" +series: "xenial" + +machines: + 0: + +applications: + mariadb: + charm: "mariadb-k8s" + series: "xenial" + scale: 2 + casandra: + charm: "casnadra-k8s" + to: ["foo=bar", "foo=baz"] + hadoop: + charm: "hadoop-k8s" + to: ["foo"] +` + errors := []string{ + `expected "key=value", got "foo" for application "hadoop"`, + `bundle machines not valid for Kubernetes bundles`, + `too many placement directives for application "casandra"`, + } + + assertVerifyErrors(c, data, nil, errors) +} + +func (*bundleDataSuite) TestRequiredCharms(c *gc.C) { + bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) + c.Assert(err, gc.IsNil) + reqCharms := bd.RequiredCharms() + + c.Assert(reqCharms, gc.DeepEquals, []string{"mediawiki", "mysql"}) +} + +// testCharm returns a charm with the given name +// and relations. The relations are specified as +// a string of the form: +// +// | +// +// Within each section, each white-space separated +// relation is specified as: +// / : +// +// So, for example: +// +// testCharm("wordpress", "web:http | db:mysql") +// +// is equivalent to a charm with metadata.yaml containing +// +// name: wordpress +// description: wordpress +// provides: +// web: +// interface: http +// requires: +// db: +// interface: mysql +// +// If the charm name has a "-sub" suffix, the +// returned charm will have Meta.Subordinate = true. +func testCharm(name string, relations string) charm.Charm { + var provides, requires string + parts := strings.Split(relations, "|") + provides = parts[0] + if len(parts) > 1 { + requires = parts[1] + } + meta := &charm.Meta{ + Name: name, + Summary: name, + Description: name, + Provides: parseRelations(provides, charm.RoleProvider), + Requires: parseRelations(requires, charm.RoleRequirer), + } + if strings.HasSuffix(name, "-sub") { + meta.Subordinate = true + } + configStr := ` +options: + title: {default: My Title, description: title, type: string} + skill-level: {description: skill, type: int} +` + config, err := charm.ReadConfig(strings.NewReader(configStr)) + if err != nil { + panic(err) + } + return testCharmImpl{ + meta: meta, + config: config, + } +} + +func parseRelations(s string, role charm.RelationRole) map[string]charm.Relation { + rels := make(map[string]charm.Relation) + for _, r := range strings.Fields(s) { + parts := strings.Split(r, ":") + if len(parts) != 2 { + panic(fmt.Errorf("invalid relation specifier %q", r)) + } + name, interf := parts[0], parts[1] + rels[name] = charm.Relation{ + Name: name, + Role: role, + Interface: interf, + Scope: charm.ScopeGlobal, + } + } + return rels +} + +type testCharmImpl struct { + meta *charm.Meta + config *charm.Config + // Implement charm.Charm, but panic if anything other than + // Meta or Config methods are called. + charm.Charm +} + +func (c testCharmImpl) Meta() *charm.Meta { + return c.meta +} + +func (c testCharmImpl) Config() *charm.Config { + return c.config +} + +var verifyWithCharmsErrorsTests = []struct { + about string + data string + charms map[string]charm.Charm + + errors []string +}{{ + about: "no charms", + data: mediawikiBundle, + charms: map[string]charm.Charm{}, + errors: []string{ + `application "mediawiki" refers to non-existent charm "mediawiki"`, + `application "mysql" refers to non-existent charm "mysql"`, + }, +}, { + about: "all present and correct", + data: ` +applications: + application1: + charm: "test" + application2: + charm: "test" + application3: + charm: "test" +relations: + - ["application1:prova", "application2:reqa"] + - ["application1:reqa", "application3:prova"] + - ["application3:provb", "application2:reqb"] +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, +}, { + about: "undefined relations", + data: ` +applications: + application1: + charm: "test" + application2: + charm: "test" +relations: + - ["application1:prova", "application2:blah"] + - ["application1:blah", "application2:prova"] +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `charm "test" used by application "application1" does not define relation "blah"`, + `charm "test" used by application "application2" does not define relation "blah"`, + }, +}, { + about: "undefined applications", + data: ` +applications: + application1: + charm: "test" + application2: + charm: "test" +relations: + - ["unknown:prova", "application2:blah"] + - ["application1:blah", "unknown:prova"] +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `relation ["application1:blah" "unknown:prova"] refers to application "unknown" not defined in this bundle`, + `relation ["unknown:prova" "application2:blah"] refers to application "unknown" not defined in this bundle`, + }, +}, { + about: "equal applications", + data: ` +applications: + application1: + charm: "test" + application2: + charm: "test" +relations: + - ["application2:prova", "application2:reqa"] +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `relation ["application2:prova" "application2:reqa"] relates an application to itself`, + }, +}, { + about: "provider to provider relation", + data: ` +applications: + application1: + charm: "test" + application2: + charm: "test" +relations: + - ["application1:prova", "application2:prova"] +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `relation "application1:prova" to "application2:prova" relates provider to provider`, + }, +}, { + about: "provider to provider relation", + data: ` +applications: + application1: + charm: "test" + application2: + charm: "test" +relations: + - ["application1:reqa", "application2:reqa"] +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `relation "application1:reqa" to "application2:reqa" relates requirer to requirer`, + }, +}, { + about: "interface mismatch", + data: ` +applications: + application1: + charm: "test" + application2: + charm: "test" +relations: + - ["application1:reqa", "application2:provb"] +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `mismatched interface between "application2:provb" and "application1:reqa" ("b" vs "a")`, + }, +}, { + about: "different charms", + data: ` +applications: + application1: + charm: "test1" + application2: + charm: "test2" +relations: + - ["application1:reqa", "application2:prova"] +`, + charms: map[string]charm.Charm{ + "test1": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + "test2": testCharm("test", ""), + }, + errors: []string{ + `charm "test2" used by application "application2" does not define relation "prova"`, + }, +}, { + about: "ambiguous relation", + data: ` +applications: + application1: + charm: "test1" + application2: + charm: "test2" +relations: + - [application1, application2] +`, + charms: map[string]charm.Charm{ + "test1": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + "test2": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `cannot infer endpoint between application1 and application2: ambiguous relation: application1 application2 could refer to "application1:prova application2:reqa"; "application1:provb application2:reqb"; "application1:reqa application2:prova"; "application1:reqb application2:provb"`, + }, +}, { + about: "relation using juju-info", + data: ` +applications: + application1: + charm: "provider" + application2: + charm: "requirer" +relations: + - [application1, application2] +`, + charms: map[string]charm.Charm{ + "provider": testCharm("provider", ""), + "requirer": testCharm("requirer", "| req:juju-info"), + }, +}, { + about: "ambiguous when implicit relations taken into account", + data: ` +applications: + application1: + charm: "provider" + application2: + charm: "requirer" +relations: + - [application1, application2] +`, + charms: map[string]charm.Charm{ + "provider": testCharm("provider", "provdb:db | "), + "requirer": testCharm("requirer", "| reqdb:db reqinfo:juju-info"), + }, +}, { + about: "half of relation left open", + data: ` +applications: + application1: + charm: "provider" + application2: + charm: "requirer" +relations: + - ["application1:prova2", application2] +`, + charms: map[string]charm.Charm{ + "provider": testCharm("provider", "prova1:a prova2:a | "), + "requirer": testCharm("requirer", "| reqa:a"), + }, +}, { + about: "duplicate relation between open and fully-specified relations", + data: ` +applications: + application1: + charm: "provider" + application2: + charm: "requirer" +relations: + - ["application1:prova", "application2:reqa"] + - ["application1", "application2"] +`, + charms: map[string]charm.Charm{ + "provider": testCharm("provider", "prova:a | "), + "requirer": testCharm("requirer", "| reqa:a"), + }, + errors: []string{ + `relation ["application1" "application2"] is defined more than once`, + }, +}, { + about: "configuration options specified", + data: ` +applications: + application1: + charm: "test" + options: + title: "some title" + skill-level: 245 + application2: + charm: "test" + options: + title: "another title" +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, +}, { + about: "invalid type for option", + data: ` +applications: + application1: + charm: "test" + options: + title: "some title" + skill-level: "too much" + application2: + charm: "test" + options: + title: "another title" +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `cannot validate application "application1": option "skill-level" expected int, got "too much"`, + }, +}, { + about: "unknown option", + data: ` +applications: + application1: + charm: "test" + options: + title: "some title" + unknown-option: 2345 +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `cannot validate application "application1": configuration option "unknown-option" not found in charm "test"`, + }, +}, { + about: "multiple config problems", + data: ` +applications: + application1: + charm: "test" + options: + title: "some title" + unknown-option: 2345 + application2: + charm: "test" + options: + title: 123 + another-unknown: 2345 +`, + charms: map[string]charm.Charm{ + "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), + }, + errors: []string{ + `cannot validate application "application1": configuration option "unknown-option" not found in charm "test"`, + `cannot validate application "application2": configuration option "another-unknown" not found in charm "test"`, + `cannot validate application "application2": option "title" expected string, got 123`, + }, +}, { + about: "subordinate charm with more than zero units", + data: ` +applications: + testsub: + charm: "testsub" + num_units: 1 +`, + charms: map[string]charm.Charm{ + "testsub": testCharm("test-sub", ""), + }, + errors: []string{ + `application "testsub" is subordinate but has non-zero num_units`, + }, +}, { + about: "subordinate charm with more than one unit", + data: ` +applications: + testsub: + charm: "testsub" + num_units: 1 +`, + charms: map[string]charm.Charm{ + "testsub": testCharm("test-sub", ""), + }, + errors: []string{ + `application "testsub" is subordinate but has non-zero num_units`, + }, +}, { + about: "subordinate charm with to-clause", + data: ` +applications: + testsub: + charm: "testsub" + to: [0] +machines: + 0: +`, + charms: map[string]charm.Charm{ + "testsub": testCharm("test-sub", ""), + }, + errors: []string{ + `application "testsub" is subordinate but specifies unit placement`, + `too many units specified in unit placement for application "testsub"`, + }, +}, { + about: "charm with unspecified units and more than one to: entry", + data: ` +applications: + test: + charm: "test" + to: [0, 1] +machines: + 0: + 1: +`, + errors: []string{ + `too many units specified in unit placement for application "test"`, + }, +}, { + about: "charmhub charm revision and no channel", + data: ` +applications: + wordpress: + charm: "wordpress" + revision: 5 + num_units: 1 +`, + errors: []string{ + `application "wordpress" with a revision requires a channel for future upgrades, please use channel`, + }, +}, { + about: "charmhub charm revision in charm url", + data: ` +applications: + wordpress: + charm: "wordpress-9" + num_units: 1 +`, + errors: []string{ + `cannot specify revision in "ch:wordpress-9", please use revision`, + }, +}, { + about: "charmstore charm url revision value less than 0", + data: ` +applications: + wordpress: + charm: "wordpress" + revision: -5 + channel: edge + num_units: 1 +`, + errors: []string{ + `the revision for application "wordpress" must be zero or greater`, + }, +}} + +func (*bundleDataSuite) TestVerifyWithCharmsErrors(c *gc.C) { + for i, test := range verifyWithCharmsErrorsTests { + c.Logf("test %d: %s", i, test.about) + assertVerifyErrors(c, test.data, test.charms, test.errors) + } +} + +var parsePlacementTests = []struct { + placement string + expect *charm.UnitPlacement + expectErr string +}{{ + placement: "lxc:application/0", + expect: &charm.UnitPlacement{ + ContainerType: "lxc", + Application: "application", + Unit: 0, + }, +}, { + placement: "lxc:application", + expect: &charm.UnitPlacement{ + ContainerType: "lxc", + Application: "application", + Unit: -1, + }, +}, { + placement: "lxc:99", + expect: &charm.UnitPlacement{ + ContainerType: "lxc", + Machine: "99", + Unit: -1, + }, +}, { + placement: "lxc:new", + expect: &charm.UnitPlacement{ + ContainerType: "lxc", + Machine: "new", + Unit: -1, + }, +}, { + placement: "application/0", + expect: &charm.UnitPlacement{ + Application: "application", + Unit: 0, + }, +}, { + placement: "application", + expect: &charm.UnitPlacement{ + Application: "application", + Unit: -1, + }, +}, { + placement: "application45", + expect: &charm.UnitPlacement{ + Application: "application45", + Unit: -1, + }, +}, { + placement: "99", + expect: &charm.UnitPlacement{ + Machine: "99", + Unit: -1, + }, +}, { + placement: "new", + expect: &charm.UnitPlacement{ + Machine: "new", + Unit: -1, + }, +}, { + placement: ":0", + expectErr: `invalid placement syntax ":0"`, +}, { + placement: "05", + expectErr: `invalid placement syntax "05"`, +}, { + placement: "new/2", + expectErr: `invalid placement syntax "new/2"`, +}} + +func (*bundleDataSuite) TestParsePlacement(c *gc.C) { + for i, test := range parsePlacementTests { + c.Logf("test %d: %q", i, test.placement) + up, err := charm.ParsePlacement(test.placement) + if test.expectErr != "" { + c.Assert(err, gc.ErrorMatches, test.expectErr) + } else { + c.Assert(err, gc.IsNil) + c.Assert(up, jc.DeepEquals, test.expect) + } + } +} + +// Tests that empty/nil applications cause an error +func (*bundleDataSuite) TestApplicationEmpty(c *gc.C) { + tstDatas := []string{ + ` +applications: + application1: + application2: + charm: "test" + plan: "testisv/test2" +`, + ` +applications: + application1: + charm: "test" + plan: "testisv/test2" + application2: +`, + ` +applications: + application1: + charm: "test" + plan: "testisv/test2" + application2: ~ +`, + } + + for _, d := range tstDatas { + bd, err := charm.ReadBundleData(strings.NewReader(d)) + c.Assert(err, gc.IsNil) + + err = bd.Verify(nil, nil, nil) + c.Assert(err, gc.ErrorMatches, "bundle application for key .+ is undefined") + } +} + +func (*bundleDataSuite) TestApplicationPlans(c *gc.C) { + data := ` +applications: + application1: + charm: "test" + plan: "testisv/test" + application2: + charm: "test" + plan: "testisv/test2" + application3: + charm: "test" + plan: "default" +relations: + - ["application1:prova", "application2:reqa"] + - ["application1:reqa", "application3:prova"] + - ["application3:provb", "application2:reqb"] +` + + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + + c.Assert(bd.Applications, jc.DeepEquals, map[string]*charm.ApplicationSpec{ + "application1": { + Charm: "test", + Plan: "testisv/test", + }, + "application2": { + Charm: "test", + Plan: "testisv/test2", + }, + "application3": { + Charm: "test", + Plan: "default", + }, + }) + +} diff --git a/internal/charm/bundledatasrc.go b/internal/charm/bundledatasrc.go new file mode 100644 index 00000000000..126d737974f --- /dev/null +++ b/internal/charm/bundledatasrc.go @@ -0,0 +1,270 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "bytes" + "io" + "os" + "path/filepath" + "strings" + + "github.com/juju/errors" + "gopkg.in/yaml.v2" +) + +// FieldPresenceMap indicates which keys of a parsed bundle yaml document were +// present when the document was parsed. This map is used by the overlay merge +// code to figure out whether empty/nil field values were actually specified as +// such in the yaml document. +type FieldPresenceMap map[interface{}]interface{} + +func (fpm FieldPresenceMap) fieldPresent(fieldName string) bool { + _, exists := fpm[fieldName] + return exists +} + +func (fpm FieldPresenceMap) forField(fieldName string) FieldPresenceMap { + v, exists := fpm[fieldName] + if !exists { + return nil + } + + // Always returns a FieldPresenceMap even if the underlying type is empty. + // As the only way to interact with the map is through the use of the two + // methods, then it will allow you to walk over the map in a much saner way. + asMap, _ := v.(FieldPresenceMap) + if asMap == nil { + return FieldPresenceMap{} + } + return asMap +} + +// BundleDataPart combines a parsed BundleData instance with a nested map that +// can be used to discriminate between fields that are missing from the data +// and those that are present but defined to be empty. +type BundleDataPart struct { + Data *BundleData + PresenceMap FieldPresenceMap + UnmarshallError error +} + +// BundleDataSource is implemented by types that can parse bundle data into a +// list of composable parts. +type BundleDataSource interface { + Parts() []*BundleDataPart + BundleBytes() []byte + BasePath() string + ResolveInclude(path string) ([]byte, error) +} + +type resolvedBundleDataSource struct { + basePath string + bundleBytes []byte + parts []*BundleDataPart +} + +func (s *resolvedBundleDataSource) Parts() []*BundleDataPart { + return s.parts +} + +func (s *resolvedBundleDataSource) BundleBytes() []byte { + return s.bundleBytes +} + +func (s *resolvedBundleDataSource) BasePath() string { + return s.basePath +} + +func (s *resolvedBundleDataSource) ResolveInclude(path string) ([]byte, error) { + absPath := path + if !filepath.IsAbs(absPath) { + var err error + absPath, err = filepath.Abs(filepath.Clean(filepath.Join(s.basePath, absPath))) + if err != nil { + return nil, errors.Annotatef(err, "resolving relative include %q", path) + } + } + + info, err := os.Stat(absPath) + if err != nil { + if isNotExistsError(err) { + return nil, errors.NotFoundf("include file %q", absPath) + } + + return nil, errors.Annotatef(err, "stat failed for %q", absPath) + } + + if info.IsDir() { + return nil, errors.Errorf("include path %q resolves to a folder", absPath) + } + + data, err := os.ReadFile(absPath) + if err != nil { + return nil, errors.Annotatef(err, "reading include file at %q", absPath) + } + + return data, nil +} + +// LocalBundleDataSource reads a (potentially multi-part) bundle from path and +// returns a BundleDataSource for it. Path may point to a yaml file, a bundle +// directory or a bundle archive. +func LocalBundleDataSource(path string) (BundleDataSource, error) { + info, err := os.Stat(path) + if err != nil { + if isNotExistsError(err) { + return nil, errors.NotFoundf("%q", path) + } + + return nil, errors.Annotatef(err, "stat failed for %q", path) + } + + // Treat as an exploded bundle archive directory + if info.IsDir() { + path = filepath.Join(path, "bundle.yaml") + } + + // Try parsing as a yaml file first + f, err := os.Open(path) + if err != nil { + if isNotExistsError(err) { + return nil, errors.NotFoundf("%q", path) + } + return nil, errors.Annotatef(err, "access bundle data at %q", path) + } + defer func() { _ = f.Close() }() + + b, err := io.ReadAll(f) + if err != nil { + return nil, err + } + parts, pErr := parseBundleParts(b) + if pErr == nil { + absPath, err := filepath.Abs(path) + if err != nil { + return nil, errors.Annotatef(err, "resolve absolute path to %s", path) + } + return &resolvedBundleDataSource{ + basePath: filepath.Dir(absPath), + parts: parts, + bundleBytes: b, + }, nil + } + + // As a fallback, try to parse as a bundle archive + zo := newZipOpenerFromPath(path) + zrc, err := zo.openZip() + if err != nil { + // Not a zip file; return the original parse error + return nil, errors.NewNotValid(pErr, "cannot unmarshal bundle contents") + } + defer func() { _ = zrc.Close() }() + + r, err := zipOpenFile(zrc, "bundle.yaml") + if err != nil { + // It is a zip file but not one that contains a bundle.yaml + return nil, errors.NotFoundf("interpret bundle contents as a bundle archive: %v", err) + } + defer func() { _ = r.Close() }() + + b, err = io.ReadAll(r) + if err != nil { + return nil, err + } + if parts, pErr = parseBundleParts(b); pErr == nil { + return &resolvedBundleDataSource{ + basePath: "", // use empty base path for archives + parts: parts, + bundleBytes: b, + }, nil + } + + return nil, errors.NewNotValid(pErr, "cannot unmarshal bundle contents") +} + +func isNotExistsError(err error) bool { + if os.IsNotExist(err) { + return true + } + // On Windows, we get a path error due to a GetFileAttributesEx syscall. + // To avoid being too proscriptive, we'll simply check for the error + // type and not any content. + if _, ok := err.(*os.PathError); ok { + return true + } + return false +} + +// StreamBundleDataSource reads a (potentially multi-part) bundle from r and +// returns a BundleDataSource for it. +func StreamBundleDataSource(r io.Reader, basePath string) (BundleDataSource, error) { + b, err := io.ReadAll(r) + if err != nil { + return nil, err + } + parts, err := parseBundleParts(b) + if err != nil { + return nil, errors.NotValidf("cannot unmarshal bundle contents: %v", err) + } + + return &resolvedBundleDataSource{parts: parts, bundleBytes: b, basePath: basePath}, nil +} + +func parseBundleParts(b []byte) ([]*BundleDataPart, error) { + var ( + // Ideally, we would be using a single reader and we would + // rewind it to read each block in structured and raw mode. + // Unfortunately, the yaml parser seems to parse all documents + // at once so we need to use two decoders. The third is to allow + // for validation of the yaml by using strict decoding. However + // we still want to return non strict bundle parts so that + // force may be used in deploy. + structDec = yaml.NewDecoder(bytes.NewReader(b)) + strictDec = yaml.NewDecoder(bytes.NewReader(b)) + rawDec = yaml.NewDecoder(bytes.NewReader(b)) + parts []*BundleDataPart + ) + + for docIdx := 0; ; docIdx++ { + var part BundleDataPart + + err := structDec.Decode(&part.Data) + if err == io.EOF { + break + } else if err != nil && !strings.HasPrefix(err.Error(), "yaml: unmarshal errors:") { + return nil, errors.Annotatef(err, "unmarshal document %d", docIdx) + } + + var data *BundleData + strictDec.SetStrict(true) + err = strictDec.Decode(&data) + if err == io.EOF { + break + } else if err != nil { + if strings.HasPrefix(err.Error(), "yaml: unmarshal errors:") { + friendlyErrors := userFriendlyUnmarshalErrors(err) + part.UnmarshallError = errors.Annotatef(friendlyErrors, "unmarshal document %d", docIdx) + } else { + return nil, errors.Annotatef(err, "unmarshal document %d", docIdx) + } + } + + // We have already checked for errors for the previous unmarshal attempt + _ = rawDec.Decode(&part.PresenceMap) + parts = append(parts, &part) + } + + return parts, nil +} + +func userFriendlyUnmarshalErrors(err error) error { + friendlyText := err.Error() + friendlyText = strings.ReplaceAll(friendlyText, "type charm.ApplicationSpec", "applications") + friendlyText = strings.ReplaceAll(friendlyText, "type charm.legacyBundleData", "bundle") + friendlyText = strings.ReplaceAll(friendlyText, "type charm.RelationSpec", "relations") + friendlyText = strings.ReplaceAll(friendlyText, "type charm.MachineSpec", "machines") + friendlyText = strings.ReplaceAll(friendlyText, "type charm.SaasSpec", "saas") + return errors.New(friendlyText) +} diff --git a/internal/charm/bundledatasrc_test.go b/internal/charm/bundledatasrc_test.go new file mode 100644 index 00000000000..5f887b87d76 --- /dev/null +++ b/internal/charm/bundledatasrc_test.go @@ -0,0 +1,256 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "archive/zip" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" +) + +type BundleDataSourceSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&BundleDataSourceSuite{}) + +var bundlePath = "internal/test-charm-repo/bundle/wordpress-multidoc/bundle.yaml" + +func (s *BundleDataSourceSuite) TestReadBundleFromLocalFile(c *gc.C) { + path := bundleDirPath(c, "wordpress-multidoc") + src, err := LocalBundleDataSource(filepath.Join(path, "bundle.yaml")) + c.Assert(err, gc.IsNil) + + raw, err := os.ReadFile(bundlePath) + c.Assert(err, jc.ErrorIsNil) + assertBundleSourceProcessed(c, src, string(raw)) +} + +func (s *BundleDataSourceSuite) TestReadBundleFromExplodedArchiveFolder(c *gc.C) { + path := bundleDirPath(c, "wordpress-multidoc") + src, err := LocalBundleDataSource(path) + c.Assert(err, gc.IsNil) + + raw, err := os.ReadFile(bundlePath) + c.Assert(err, jc.ErrorIsNil) + assertBundleSourceProcessed(c, src, string(raw)) +} + +func (s *BundleDataSourceSuite) TestReadBundleFromArchive(c *gc.C) { + path := archiveBundleDirPath(c, "wordpress-multidoc") + src, err := LocalBundleDataSource(path) + c.Assert(err, gc.IsNil) + + raw, err := os.ReadFile(bundlePath) + c.Assert(err, jc.ErrorIsNil) + assertBundleSourceProcessed(c, src, string(raw)) +} + +func (s *BundleDataSourceSuite) TestReadBundleFromStream(c *gc.C) { + bundle := ` +applications: + wordpress: + charm: wordpress + mysql: + charm: mysql + num_units: 1 +relations: + - ["wordpress:db", "mysql:server"] +--- # overlay.yaml +applications: + wordpress: + offers: + offer1: + endpoints: + - "some-endpoint" +--- # overlay2.yaml +applications: + wordpress: + offers: + offer1: + acl: + admin: "admin" + foo: "consume" +` + + src, err := StreamBundleDataSource(strings.NewReader(bundle), "https://example.com") + c.Assert(err, gc.IsNil) + assertBundleSourceProcessed(c, src, bundle) +} + +func assertBundleSourceProcessed(c *gc.C, src BundleDataSource, bundle string) { + parts := src.Parts() + c.Assert(parts, gc.HasLen, 3) + assertFieldPresent(c, parts[1], "applications.wordpress.offers.offer1.endpoints") + assertFieldPresent(c, parts[2], "applications.wordpress.offers.offer1.acl.admin") + c.Assert(string(src.BundleBytes()), gc.Equals, bundle) +} + +func assertFieldPresent(c *gc.C, part *BundleDataPart, path string) { + var ( + segments = strings.Split(path, ".") + next interface{} = part.PresenceMap + ) + + for segIndex, segment := range segments { + c.Assert(next, gc.NotNil, gc.Commentf("incomplete path: %s", strings.Join(segments[:segIndex], "."))) + switch typ := next.(type) { + case FieldPresenceMap: + next = typ[segment] + c.Assert(next, gc.NotNil, gc.Commentf("incomplete path: %s", strings.Join(segments[:segIndex+1], "."))) + default: + c.Fatalf("unexpected type %T at path: %s", typ, strings.Join(segments[:segIndex], ".")) + } + } +} + +func (s *BundleDataSourceSuite) TestParseBundlePartsStrict(c *gc.C) { + b := []byte(` +applications: + wordpress: + charm: wordpress + constrain: "mem=8G" + mysql: + charm: mysql + num_uns: 1 +relations: + - ["wordpress:db", "mysql:server"] +--- # overlay.yaml +applications: + wordpress: + offers: + offer1: + endpoints: + - "some-endpoint" +--- # overlay2.yaml +applications: + wordpress: + offer: + offer1: + acl: + admin: "admin" + foo: "consume" +`) + + parts, err := parseBundleParts(b) + c.Assert(err, gc.IsNil) + c.Assert(parts, gc.HasLen, 3) + c.Assert(parts[0].UnmarshallError, gc.NotNil) + c.Assert(parts[0].UnmarshallError.Error(), gc.Matches, ""+ + "unmarshal document 0: yaml: unmarshal errors:\n"+ + " line 5: field constrain not found in applications\n"+ + " line 8: field num_uns not found in applications") + c.Assert(parts[1].UnmarshallError, jc.ErrorIsNil) + c.Assert(parts[2].UnmarshallError, gc.NotNil) + c.Assert(parts[2].UnmarshallError.Error(), gc.Matches, ""+ + "unmarshal document 2: yaml: unmarshal errors:\n"+ + " line 21: field offer not found in applications") +} + +func (s *BundleDataSourceSuite) TestResolveAbsoluteFileInclude(c *gc.C) { + target, err := filepath.Abs(filepath.Join(c.MkDir(), "example")) + c.Assert(err, gc.IsNil) + + expContent := "example content\n" + c.Assert(os.WriteFile(target, []byte(expContent), os.ModePerm), gc.IsNil) + + ds := new(resolvedBundleDataSource) + + got, err := ds.ResolveInclude(target) + c.Assert(err, gc.IsNil) + c.Assert(string(got), gc.Equals, expContent) +} + +func (s *BundleDataSourceSuite) TestResolveRelativeFileInclude(c *gc.C) { + relTo := c.MkDir() + target, err := filepath.Abs(filepath.Join(relTo, "example")) + c.Assert(err, gc.IsNil) + + expContent := "example content\n" + c.Assert(os.WriteFile(target, []byte(expContent), os.ModePerm), gc.IsNil) + + ds := &resolvedBundleDataSource{ + basePath: relTo, + } + + got, err := ds.ResolveInclude("./example") + c.Assert(err, gc.IsNil) + c.Assert(string(got), gc.Equals, expContent) +} + +func (s *BundleDataSourceSuite) TestResolveIncludeErrors(c *gc.C) { + cwd, err := os.Getwd() + c.Assert(err, gc.IsNil) + + tmpDir := c.MkDir() + specs := []struct { + descr string + incPath string + exp string + }{ + { + descr: "abs path does not exist", + incPath: "/some/invalid/path", + exp: `include file "/some/invalid/path" not found`, + }, + { + descr: "rel path does not exist", + incPath: "./missing", + exp: `include file "` + cwd + `/missing" not found`, + }, + { + descr: "path points to directory", + incPath: tmpDir, + exp: fmt.Sprintf("include path %q resolves to a folder", tmpDir), + }, + } + + ds := new(resolvedBundleDataSource) + for specIndex, spec := range specs { + c.Logf("[test %d] %s", specIndex, spec.descr) + + _, err := ds.ResolveInclude(spec.incPath) + c.Assert(err, gc.Not(gc.IsNil)) + + c.Assert(err.Error(), gc.Equals, spec.exp) + } +} + +func bundleDirPath(c *gc.C, name string) string { + path := filepath.Join("internal/test-charm-repo/bundle", name) + assertIsDir(c, path) + return path +} + +func assertIsDir(c *gc.C, path string) { + info, err := os.Stat(path) + c.Assert(err, gc.IsNil) + c.Assert(info.IsDir(), gc.Equals, true) +} + +func archiveBundleDirPath(c *gc.C, name string) string { + src := filepath.Join("internal/test-charm-repo/bundle", name, "bundle.yaml") + srcYaml, err := os.ReadFile(src) + c.Assert(err, gc.IsNil) + + dstPath := filepath.Join(c.MkDir(), "bundle.zip") + f, err := os.Create(dstPath) + c.Assert(err, gc.IsNil) + defer func() { c.Assert(f.Close(), gc.IsNil) }() + + zw := zip.NewWriter(f) + defer func() { c.Assert(zw.Close(), gc.IsNil) }() + w, err := zw.Create("bundle.yaml") + c.Assert(err, gc.IsNil) + _, err = w.Write(srcYaml) + c.Assert(err, gc.IsNil) + + return dstPath +} diff --git a/internal/charm/bundledir.go b/internal/charm/bundledir.go new file mode 100644 index 00000000000..34ef74d55c1 --- /dev/null +++ b/internal/charm/bundledir.go @@ -0,0 +1,84 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/juju/juju/core/logger" +) + +// BundleDir defines a bundle from a given directory. +type BundleDir struct { + Path string + data *BundleData + bundleBytes []byte + readMe string + + containsOverlays bool + logger logger.Logger +} + +// Trick to ensure *BundleDir implements the Bundle interface. +var _ Bundle = (*BundleDir)(nil) + +// ReadBundleDir returns a BundleDir representing an expanded +// bundle directory. It does not verify the bundle data. +func ReadBundleDir(path string, options ...ReadOption) (dir *BundleDir, err error) { + opts := newReadOptions(options) + + dir = &BundleDir{ + Path: path, + logger: opts.logger, + } + b, err := os.ReadFile(dir.join("bundle.yaml")) + if err != nil { + return nil, err + } + dir.bundleBytes = b + dir.data, dir.containsOverlays, err = readBaseFromMultidocBundle(b) + if err != nil { + return nil, err + } + readMe, err := os.ReadFile(dir.join("README.md")) + if err != nil { + return nil, fmt.Errorf("cannot read README file: %v", err) + } + dir.readMe = string(readMe) + return dir, nil +} + +// Data returns the contents of the bundle's bundle.yaml file. +func (dir *BundleDir) Data() *BundleData { + return dir.data +} + +// BundleBytes implements Bundle.BundleBytes +func (dir *BundleDir) BundleBytes() []byte { + return dir.bundleBytes +} + +// ReadMe returns the contents of the bundle's README.md file. +func (dir *BundleDir) ReadMe() string { + return dir.readMe +} + +// ContainsOverlays returns true if the bundle contains any overlays. +func (dir *BundleDir) ContainsOverlays() bool { + return dir.containsOverlays +} + +func (dir *BundleDir) ArchiveTo(w io.Writer) error { + return writeArchive(w, dir.Path, -1, "", nil, nil, dir.logger) +} + +// join builds a path rooted at the bundle's expanded directory +// path and the extra path components provided. +func (dir *BundleDir) join(parts ...string) string { + parts = append([]string{dir.Path}, parts...) + return filepath.Join(parts...) +} diff --git a/internal/charm/bundledir_test.go b/internal/charm/bundledir_test.go new file mode 100644 index 00000000000..5bb58528c55 --- /dev/null +++ b/internal/charm/bundledir_test.go @@ -0,0 +1,58 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "os" + "path/filepath" + + "github.com/juju/testing" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +type BundleDirSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&BundleDirSuite{}) + +func (s *BundleDirSuite) TestReadBundleDir(c *gc.C) { + path := bundleDirPath(c, "wordpress-simple") + dir, err := charm.ReadBundleDir(path) + c.Assert(err, gc.IsNil) + checkWordpressBundle(c, dir, path, "wordpress-simple") +} + +func (s *BundleDirSuite) TestReadBundleDirWithoutREADME(c *gc.C) { + path := cloneDir(c, bundleDirPath(c, "wordpress-simple")) + err := os.Remove(filepath.Join(path, "README.md")) + c.Assert(err, gc.IsNil) + dir, err := charm.ReadBundleDir(path) + c.Assert(err, gc.ErrorMatches, "cannot read README file: .*") + c.Assert(dir, gc.IsNil) +} + +func (s *BundleDirSuite) TestArchiveTo(c *gc.C) { + baseDir := c.MkDir() + charmDir := cloneDir(c, bundleDirPath(c, "wordpress-simple")) + s.assertArchiveTo(c, baseDir, charmDir) +} + +func (s *BundleDirSuite) assertArchiveTo(c *gc.C, baseDir, bundleDir string) { + dir, err := charm.ReadBundleDir(bundleDir) + c.Assert(err, gc.IsNil) + path := filepath.Join(baseDir, "archive.bundle") + file, err := os.Create(path) + c.Assert(err, gc.IsNil) + err = dir.ArchiveTo(file) + file.Close() + c.Assert(err, gc.IsNil) + + archive, err := charm.ReadBundleArchive(path) + c.Assert(err, gc.IsNil) + c.Assert(archive.ReadMe(), gc.Equals, dir.ReadMe()) + c.Assert(archive.Data(), gc.DeepEquals, dir.Data()) +} diff --git a/internal/charm/channel.go b/internal/charm/channel.go new file mode 100644 index 00000000000..74dc51aa1fd --- /dev/null +++ b/internal/charm/channel.go @@ -0,0 +1,179 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "strings" + + "github.com/juju/errors" +) + +// Risk describes the type of risk in a current channel. +type Risk string + +const ( + Stable Risk = "stable" + Candidate Risk = "candidate" + Beta Risk = "beta" + Edge Risk = "edge" +) + +// Risks is a list of the available channel risks. +var Risks = []Risk{ + Stable, + Candidate, + Beta, + Edge, +} + +func isRisk(potential string) bool { + for _, risk := range Risks { + if potential == string(risk) { + return true + } + } + return false +} + +// Channel identifies and describes completely a store channel. +// +// A channel consists of, and is subdivided by, tracks, risk-levels and +// branches: +// - Tracks enable snap developers to publish multiple supported releases of +// their application under the same snap name. +// - Risk-levels represent a progressive potential trade-off between stability +// and new features. +// - Branches are _optional_ and hold temporary releases intended to help with +// bug-fixing. +// +// The complete channel name can be structured as three distinct parts separated +// by slashes: +// +// // +// +type Channel struct { + Track string `json:"track,omitempty"` + Risk Risk `json:"risk,omitempty"` + Branch string `json:"branch,omitempty"` +} + +// MakeChannel creates a core charm Channel from a set of component parts. +func MakeChannel(track, risk, branch string) (Channel, error) { + if !isRisk(risk) { + return Channel{}, errors.NotValidf("risk %q", risk) + } + return Channel{ + Track: track, + Risk: Risk(risk), + Branch: branch, + }, nil +} + +// MakePermissiveChannel creates a normalized core charm channel which +// never fails. It assumes that the risk has been prechecked. +func MakePermissiveChannel(track, risk, branch string) Channel { + ch := Channel{ + Track: track, + Risk: Risk(risk), + Branch: branch, + } + return ch.Normalize() +} + +// ParseChannel parses a string representing a store channel. +func ParseChannel(s string) (Channel, error) { + if s == "" { + return Channel{}, errors.NotValidf("empty channel") + } + + p := strings.Split(s, "/") + + var risk, track, branch *string + switch len(p) { + case 1: + if isRisk(p[0]) { + risk = &p[0] + } else { + track = &p[0] + } + case 2: + if isRisk(p[0]) { + risk, branch = &p[0], &p[1] + } else { + track, risk = &p[0], &p[1] + } + case 3: + track, risk, branch = &p[0], &p[1], &p[2] + default: + return Channel{}, errors.Errorf("channel is malformed and has too many components %q", s) + } + + ch := Channel{} + + if risk != nil { + if !isRisk(*risk) { + return Channel{}, errors.NotValidf("risk in channel %q", s) + } + // We can lift this into a risk, as we've validated prior to this to + // ensure it's a valid risk. + ch.Risk = Risk(*risk) + } + if track != nil { + if *track == "" { + return Channel{}, errors.NotValidf("track in channel %q", s) + } + ch.Track = *track + } + if branch != nil { + if *branch == "" { + return Channel{}, errors.NotValidf("branch in channel %q", s) + } + ch.Branch = *branch + } + return ch, nil +} + +// ParseChannelNormalize parses a string representing a store channel. +// The returned channel's track, risk and name are normalized. +func ParseChannelNormalize(s string) (Channel, error) { + ch, err := ParseChannel(s) + if err != nil { + return Channel{}, errors.Trace(err) + } + return ch.Normalize(), nil +} + +// Normalize the channel with normalized track, risk and names. +func (ch Channel) Normalize() Channel { + track := ch.Track + + risk := ch.Risk + if risk == "" { + risk = "stable" + } + + return Channel{ + Track: track, + Risk: risk, + Branch: ch.Branch, + } +} + +// Empty returns true if all it's components are empty. +func (ch Channel) Empty() bool { + return ch.Track == "" && ch.Risk == "" && ch.Branch == "" +} + +func (ch Channel) String() string { + path := string(ch.Risk) + if track := ch.Track; track != "" { + path = fmt.Sprintf("%s/%s", track, path) + } + if branch := ch.Branch; branch != "" { + path = fmt.Sprintf("%s/%s", path, branch) + } + + return path +} diff --git a/internal/charm/channel_test.go b/internal/charm/channel_test.go new file mode 100644 index 00000000000..96e43d69dce --- /dev/null +++ b/internal/charm/channel_test.go @@ -0,0 +1,194 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "github.com/juju/errors" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +type channelSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&channelSuite{}) + +func (s channelSuite) TestParseChannelNormalize(c *gc.C) { + // ParseChannelNormalize tests ParseChannel as well. + tests := []struct { + Name string + Value string + Expected charm.Channel + ExpectedErr string + }{{ + Name: "empty", + Value: "", + ExpectedErr: "empty channel not valid", + }, { + Name: "empty components", + Value: "//", + ExpectedErr: `risk in channel "//" not valid`, + }, { + Name: "too many components", + Value: "////", + ExpectedErr: `channel is malformed and has too many components "////"`, + }, { + Name: "invalid risk", + Value: "track/meshuggah", + ExpectedErr: `risk in channel "track/meshuggah" not valid`, + }, { + Name: "risk", + Value: "stable", + Expected: charm.Channel{ + Risk: "stable", + }, + }, { + Name: "track", + Value: "meshuggah", + Expected: charm.Channel{ + Track: "meshuggah", + Risk: "stable", + }, + }, { + Name: "risk and branch", + Value: "stable/foo", + Expected: charm.Channel{ + Risk: "stable", + Branch: "foo", + }, + }, { + Name: "track and risk", + Value: "foo/stable", + Expected: charm.Channel{ + Track: "foo", + Risk: "stable", + }, + }, { + Name: "track, risk and branch", + Value: "foo/stable/bar", + Expected: charm.Channel{ + Track: "foo", + Risk: "stable", + Branch: "bar", + }, + }} + for k, test := range tests { + c.Logf("test %q at %d", test.Name, k) + ch, err := charm.ParseChannelNormalize(test.Value) + if test.ExpectedErr != "" { + c.Assert(err, gc.ErrorMatches, test.ExpectedErr) + } else { + c.Assert(ch, gc.DeepEquals, test.Expected) + c.Assert(err, gc.IsNil) + } + } +} + +func (s channelSuite) TestString(c *gc.C) { + tests := []struct { + Name string + Value string + Expected string + }{{ + Name: "risk", + Value: "stable", + Expected: "stable", + }, { + Name: "latest track", + Value: "latest/stable", + Expected: "latest/stable", + }, { + Name: "track", + Value: "1.0", + Expected: "1.0/stable", + }, { + Name: "track and risk", + Value: "1.0/edge", + Expected: "1.0/edge", + }, { + Name: "track, risk and branch", + Value: "1.0/edge/foo", + Expected: "1.0/edge/foo", + }, { + Name: "latest, risk and branch", + Value: "latest/edge/foo", + Expected: "latest/edge/foo", + }} + for k, test := range tests { + c.Logf("test %q at %d", test.Name, k) + ch, err := charm.ParseChannelNormalize(test.Value) + c.Assert(err, gc.IsNil) + c.Assert(ch.String(), gc.DeepEquals, test.Expected) + } +} + +func (s channelSuite) TestMakeChannel(c *gc.C) { + tests := []struct { + Name string + Track string + Risk string + Branch string + Expected string + ErrorType func(err error) bool + }{{ + Name: "track, risk, branch not normalized", + Track: "latest", + Risk: "beta", + Branch: "bar", + Expected: "latest/beta/bar", + ErrorType: nil, + }, { + Name: "", + Track: "", + Risk: "testme", + Branch: "", + ErrorType: errors.IsNotValid, + }} + for k, test := range tests { + c.Logf("test %q at %d", test.Name, k) + ch, err := charm.MakeChannel(test.Track, test.Risk, test.Branch) + if test.ErrorType == nil { + c.Assert(err, jc.ErrorIsNil) + c.Assert(ch, gc.DeepEquals, charm.Channel{ + Track: test.Track, + Risk: charm.Risk(test.Risk), + Branch: test.Branch, + }) + } else { + c.Assert(err, jc.Satisfies, errors.IsNotValid) + } + } +} + +func (s channelSuite) TestMakePermissiveChannelAndEmpty(c *gc.C) { + tests := []struct { + Name string + Track string + Risk string + Expected string + }{{ + Name: "latest track, risk", + Track: "latest", + Risk: "beta", + Expected: "latest/beta", + }, { + Name: "risk not valid", + Track: "", + Risk: "testme", + Expected: "testme", + }} + for k, test := range tests { + c.Logf("test %q at %d", test.Name, k) + ch := charm.MakePermissiveChannel(test.Track, test.Risk, "") + c.Assert(ch.String(), gc.Equals, test.Expected) + } +} + +func (s channelSuite) TestEmpty(c *gc.C) { + c.Assert(charm.Channel{}.Empty(), jc.IsTrue) +} diff --git a/internal/charm/charm.go b/internal/charm/charm.go new file mode 100644 index 00000000000..d2c98794954 --- /dev/null +++ b/internal/charm/charm.go @@ -0,0 +1,167 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "os" + "strings" + + "github.com/juju/collections/set" + "github.com/juju/errors" +) + +// CharmMeta describes methods that inform charm operation. +type CharmMeta interface { + Meta() *Meta + Manifest() *Manifest +} + +// The Charm interface is implemented by any type that +// may be handled as a charm. +type Charm interface { + CharmMeta + Config() *Config + Actions() *Actions + Revision() int +} + +// ReadCharm reads a Charm from path, which can point to either a charm archive or a +// charm directory. +func ReadCharm(path string) (charm Charm, err error) { + info, err := os.Stat(path) + if err != nil { + return nil, errors.Trace(err) + } + if info.IsDir() { + charm, err = ReadCharmDir(path) + } else { + charm, err = ReadCharmArchive(path) + } + if err != nil { + return nil, errors.Trace(err) + } + + return charm, errors.Trace(CheckMeta(charm)) +} + +// FormatSelectionReason represents the reason for a format version selection. +type FormatSelectionReason = string + +const ( + // SelectionManifest states that it found a manifest. + SelectionManifest FormatSelectionReason = "manifest" + // SelectionBases states that there was at least 1 base. + SelectionBases FormatSelectionReason = "bases" + // SelectionSeries states that there was at least 1 series. + SelectionSeries FormatSelectionReason = "series" + // SelectionContainers states that there was at least 1 container. + SelectionContainers FormatSelectionReason = "containers" +) + +var ( + // formatV2Set defines what in reality is a v2 metadata. + formatV2Set = set.NewStrings(SelectionBases, SelectionContainers) +) + +// MetaFormatReasons returns the format and why the selection was done. We can +// then inspect the reasons to understand the reasoning. +func MetaFormatReasons(ch CharmMeta) (Format, []FormatSelectionReason) { + manifest := ch.Manifest() + + // To better inform users of why a metadata selection was preferred over + // another, we deduce why a format is selected over another. + reasons := set.NewStrings() + if manifest != nil { + reasons.Add(SelectionManifest) + if len(manifest.Bases) > 0 { + reasons.Add(SelectionBases) + } + } + if len(ch.Meta().Series) > 0 { + reasons.Add(SelectionSeries) + } + + // To be a format v1 you can have no series with no bases or containers, or + // just have a series slice. + format := FormatV1 + if !reasons.Contains(SelectionSeries) && reasons.Intersection(formatV2Set).Size() > 0 { + format = FormatV2 + } + + return format, reasons.SortedValues() +} + +// MetaFormat returns the underlying format from checking the charm for the +// right values. +func MetaFormat(ch CharmMeta) Format { + format, _ := MetaFormatReasons(ch) + return format +} + +// CheckMeta determines the version of the metadata used by this charm, +// then checks that it is valid as appropriate. +func CheckMeta(ch CharmMeta) error { + format, reasons := MetaFormatReasons(ch) + return ch.Meta().Check(format, reasons...) +} + +// SeriesForCharm takes a requested series and a list of series supported by a +// charm and returns the series which is relevant. +// If the requested series is empty, then the first supported series is used, +// otherwise the requested series is validated against the supported series. +func SeriesForCharm(requestedSeries string, supportedSeries []string) (string, error) { + // Old charm with no supported series. + if len(supportedSeries) == 0 { + if requestedSeries == "" { + return "", errMissingSeries + } + return requestedSeries, nil + } + // Use the charm default. + if requestedSeries == "" { + return supportedSeries[0], nil + } + for _, s := range supportedSeries { + if s == requestedSeries { + return requestedSeries, nil + } + } + return "", &unsupportedSeriesError{requestedSeries, supportedSeries} +} + +// errMissingSeries is used to denote that SeriesForCharm could not determine +// a series because a legacy charm did not declare any. +var errMissingSeries = fmt.Errorf("series not specified and charm does not define any") + +// IsMissingSeriesError returns true if err is an errMissingSeries. +func IsMissingSeriesError(err error) bool { + return err == errMissingSeries +} + +// UnsupportedSeriesError represents an error indicating that the requested series +// is not supported by the charm. +type unsupportedSeriesError struct { + requestedSeries string + supportedSeries []string +} + +func (e *unsupportedSeriesError) Error() string { + return fmt.Sprintf( + "series %q not supported by charm, supported series are: %s", + e.requestedSeries, strings.Join(e.supportedSeries, ","), + ) +} + +// NewUnsupportedSeriesError returns an error indicating that the requested series +// is not supported by a charm. +func NewUnsupportedSeriesError(requestedSeries string, supportedSeries []string) error { + return &unsupportedSeriesError{requestedSeries, supportedSeries} +} + +// IsUnsupportedSeriesError returns true if err is an UnsupportedSeriesError. +func IsUnsupportedSeriesError(err error) bool { + _, ok := err.(*unsupportedSeriesError) + return ok +} diff --git a/internal/charm/charm_test.go b/internal/charm/charm_test.go new file mode 100644 index 00000000000..1cdf7206343 --- /dev/null +++ b/internal/charm/charm_test.go @@ -0,0 +1,297 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils/v4/fs" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/internal/charm" +) + +type CharmSuite struct { + testing.CleanupSuite +} + +var _ = gc.Suite(&CharmSuite{}) + +func (s *CharmSuite) TestReadCharm(c *gc.C) { + ch, err := charm.ReadCharm(charmDirPath(c, "dummy")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(ch.Meta().Name, gc.Equals, "dummy") + + bPath := archivePath(c, readCharmDir(c, "dummy")) + ch, err = charm.ReadCharm(bPath) + c.Assert(err, jc.ErrorIsNil) + c.Assert(ch.Meta().Name, gc.Equals, "dummy") +} + +func (s *CharmSuite) TestReadCharmDirEmptyError(c *gc.C) { + ch, err := charm.ReadCharm(c.MkDir()) + c.Assert(err, gc.NotNil) + c.Assert(ch, gc.Equals, nil) +} + +func (s *CharmSuite) TestReadCharmSeriesWithoutBases(c *gc.C) { + ch, err := charm.ReadCharm(charmDirPath(c, "format-series")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(ch, gc.NotNil) +} + +func (s *CharmSuite) TestReadCharmArchiveError(c *gc.C) { + path := filepath.Join(c.MkDir(), "path") + err := ioutil.WriteFile(path, []byte("foo"), 0644) + c.Assert(err, jc.ErrorIsNil) + ch, err := charm.ReadCharm(path) + c.Assert(err, gc.NotNil) + c.Assert(ch, gc.Equals, nil) +} + +func (s *CharmSuite) TestSeriesToUse(c *gc.C) { + tests := []struct { + series string + supportedSeries []string + seriesToUse string + err string + }{{ + series: "", + err: "series not specified and charm does not define any", + }, { + series: "trusty", + seriesToUse: "trusty", + }, { + series: "trusty", + supportedSeries: []string{"precise", "trusty"}, + seriesToUse: "trusty", + }, { + series: "", + supportedSeries: []string{"precise", "trusty"}, + seriesToUse: "precise", + }, { + series: "wily", + supportedSeries: []string{"precise", "trusty"}, + err: `series "wily" not supported by charm.*`, + }} + for _, test := range tests { + series, err := charm.SeriesForCharm(test.series, test.supportedSeries) + if test.err != "" { + c.Assert(err, gc.ErrorMatches, test.err) + continue + } + c.Assert(err, jc.ErrorIsNil) + c.Assert(series, jc.DeepEquals, test.seriesToUse) + } +} + +func (s *CharmSuite) IsUnsupportedSeriesError(c *gc.C) { + err := charm.NewUnsupportedSeriesError("series", []string{"supported"}) + c.Assert(charm.IsUnsupportedSeriesError(err), jc.IsTrue) + c.Assert(charm.IsUnsupportedSeriesError(fmt.Errorf("foo")), jc.IsFalse) +} + +func (s *CharmSuite) IsMissingSeriesError(c *gc.C) { + err := charm.MissingSeriesError() + c.Assert(charm.IsMissingSeriesError(err), jc.IsTrue) + c.Assert(charm.IsMissingSeriesError(fmt.Errorf("foo")), jc.IsFalse) +} + +type FormatSuite struct { + testing.CleanupSuite +} + +var _ = gc.Suite(&FormatSuite{}) + +func (FormatSuite) TestFormatV1NoSeries(c *gc.C) { + ch, err := charm.ReadCharm(charmDirPath(c, "format")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(ch, gc.NotNil) + + err = charm.CheckMeta(ch) + c.Assert(err, jc.ErrorIsNil) + + f := charm.MetaFormat(ch) + c.Assert(f, gc.Equals, charm.FormatV1) +} + +func (FormatSuite) TestFormatV1NoManifest(c *gc.C) { + ch, err := charm.ReadCharm(charmDirPath(c, "format-series")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(ch, gc.NotNil) + + err = charm.CheckMeta(ch) + c.Assert(err, jc.ErrorIsNil) +} + +func (FormatSuite) TestFormatV1Manifest(c *gc.C) { + ch, err := charm.ReadCharm(charmDirPath(c, "format-seriesmanifest")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(ch, gc.NotNil) + + err = charm.CheckMeta(ch) + c.Assert(err, jc.ErrorIsNil) + + f := charm.MetaFormat(ch) + c.Assert(f, gc.Equals, charm.FormatV1) +} + +func (FormatSuite) TestFormatV2ContainersNoManifest(c *gc.C) { + _, err := charm.ReadCharm(charmDirPath(c, "format-containers")) + c.Assert(err, gc.ErrorMatches, `containers without a manifest.yaml not valid`) +} + +func (FormatSuite) TestFormatV2ContainersManifest(c *gc.C) { + ch, err := charm.ReadCharm(charmDirPath(c, "format-containersmanifest")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(ch, gc.NotNil) + + err = charm.CheckMeta(ch) + c.Assert(err, jc.ErrorIsNil) + + f := charm.MetaFormat(ch) + c.Assert(f, gc.Equals, charm.FormatV2) +} + +func checkDummy(c *gc.C, f charm.Charm, path string) { + c.Assert(f.Revision(), gc.Equals, 1) + c.Assert(f.Meta().Name, gc.Equals, "dummy") + c.Assert(f.Config().Options["title"].Default, gc.Equals, "My Title") + c.Assert(f.Actions(), jc.DeepEquals, + &charm.Actions{ + ActionSpecs: map[string]charm.ActionSpec{ + "snapshot": { + Description: "Take a snapshot of the database.", + Params: map[string]interface{}{ + "type": "object", + "description": "Take a snapshot of the database.", + "title": "snapshot", + "properties": map[string]interface{}{ + "outfile": map[string]interface{}{ + "description": "The file to write out to.", + "type": "string", + "default": "foo.bz2", + }}, + "additionalProperties": false}}}}) + lpc, ok := f.(charm.LXDProfiler) + c.Assert(ok, jc.IsTrue) + c.Assert(lpc.LXDProfile(), jc.DeepEquals, &charm.LXDProfile{ + Config: map[string]string{ + "security.nesting": "true", + "security.privileged": "true", + }, + Description: "sample lxdprofile for testing", + Devices: map[string]map[string]string{ + "tun": { + "path": "/dev/net/tun", + "type": "unix-char", + }, + }, + }) + switch f := f.(type) { + case *charm.CharmArchive: + c.Assert(f.Path, gc.Equals, path) + case *charm.CharmDir: + c.Assert(f.Path, gc.Equals, path) + } +} + +type YamlHacker map[interface{}]interface{} + +func ReadYaml(r io.Reader) YamlHacker { + data, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + m := make(map[interface{}]interface{}) + err = yaml.Unmarshal(data, m) + if err != nil { + panic(err) + } + return YamlHacker(m) +} + +func (yh YamlHacker) Reader() io.Reader { + data, err := yaml.Marshal(yh) + if err != nil { + panic(err) + } + return bytes.NewBuffer(data) +} + +// charmDirPath returns the path to the charm with the +// given name in the testing repository. +func charmDirPath(c *gc.C, name string) string { + path := filepath.Join("internal/test-charm-repo/quantal", name) + assertIsDir(c, path) + return path +} + +// bundleDirPath returns the path to the bundle with the +// given name in the testing repository. +func bundleDirPath(c *gc.C, name string) string { + path := filepath.Join("internal/test-charm-repo/bundle", name) + assertIsDir(c, path) + return path +} + +func assertIsDir(c *gc.C, path string) { + info, err := os.Stat(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(info.IsDir(), gc.Equals, true) +} + +// readCharmDir returns the charm with the given +// name from the testing repository. +func readCharmDir(c *gc.C, name string) *charm.CharmDir { + path := charmDirPath(c, name) + ch, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + return ch +} + +// readBundleDir returns the bundle with the +// given name from the testing repository. +func readBundleDir(c *gc.C, name string) *charm.BundleDir { + path := bundleDirPath(c, name) + ch, err := charm.ReadBundleDir(path) + c.Assert(err, jc.ErrorIsNil) + return ch +} + +type ArchiverTo interface { + ArchiveTo(w io.Writer) error +} + +// archivePath archives the given charm or bundle +// to a newly created file and returns the path to the +// file. +func archivePath(c *gc.C, a ArchiverTo) string { + dir := c.MkDir() + path := filepath.Join(dir, "archive") + file, err := os.Create(path) + c.Assert(err, jc.ErrorIsNil) + defer file.Close() + err = a.ArchiveTo(file) + c.Assert(err, jc.ErrorIsNil) + return path +} + +// cloneDir recursively copies the path directory +// into a new directory and returns the path +// to it. +func cloneDir(c *gc.C, path string) string { + newPath := filepath.Join(c.MkDir(), filepath.Base(path)) + err := fs.Copy(path, newPath) + c.Assert(err, jc.ErrorIsNil) + return newPath +} diff --git a/internal/charm/charmarchive.go b/internal/charm/charmarchive.go new file mode 100644 index 00000000000..b4273dd5d0f --- /dev/null +++ b/internal/charm/charmarchive.go @@ -0,0 +1,325 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "archive/zip" + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strconv" + + "github.com/juju/collections/set" + "github.com/juju/errors" + ziputil "github.com/juju/utils/v4/zip" +) + +// CharmArchive type encapsulates access to data and operations +// on a charm archive. +type CharmArchive struct { + zopen zipOpener + + Path string // May be empty if CharmArchive wasn't read from a file + *charmBase +} + +// Trick to ensure *CharmArchive implements the Charm interface. +var _ Charm = (*CharmArchive)(nil) + +// ReadCharmArchive returns a CharmArchive for the charm in path. +func ReadCharmArchive(path string) (*CharmArchive, error) { + a, err := readCharmArchive(newZipOpenerFromPath(path)) + if err != nil { + return nil, err + } + a.Path = path + return a, nil +} + +// ReadCharmArchiveBytes returns a CharmArchive read from the given data. +// Make sure the archive fits in memory before using this. +func ReadCharmArchiveBytes(data []byte) (archive *CharmArchive, err error) { + zopener := newZipOpenerFromReader(bytes.NewReader(data), int64(len(data))) + return readCharmArchive(zopener) +} + +// ReadCharmArchiveFromReader returns a CharmArchive that uses +// r to read the charm. The given size must hold the number +// of available bytes in the file. +// +// Note that the caller is responsible for closing r - methods on +// the returned CharmArchive may fail after that. +func ReadCharmArchiveFromReader(r io.ReaderAt, size int64) (archive *CharmArchive, err error) { + return readCharmArchive(newZipOpenerFromReader(r, size)) +} + +func readCharmArchive(zopen zipOpener) (archive *CharmArchive, err error) { + b := &CharmArchive{ + zopen: zopen, + charmBase: &charmBase{}, + } + zipr, err := zopen.openZip() + if err != nil { + return nil, err + } + defer func() { _ = zipr.Close() }() + reader, err := zipOpenFile(zipr, "metadata.yaml") + if err != nil { + return nil, err + } + b.meta, err = ReadMeta(reader) + _ = reader.Close() + if err != nil { + return nil, err + } + + // Try to read the optional manifest.yaml, it's required to determine if + // this charm is v1 or not. + reader, err = zipOpenFile(zipr, "manifest.yaml") + if _, ok := err.(*noCharmArchiveFile); ok { + b.manifest = nil + } else if err != nil { + return nil, errors.Annotatef(err, `opening "manifest.yaml" file`) + } else { + b.manifest, err = ReadManifest(reader) + _ = reader.Close() + if err != nil { + return nil, errors.Annotatef(err, `parsing "manifest.yaml" file`) + } + } + + reader, err = zipOpenFile(zipr, "config.yaml") + if _, ok := err.(*noCharmArchiveFile); ok { + b.config = NewConfig() + } else if err != nil { + return nil, err + } else { + b.config, err = ReadConfig(reader) + _ = reader.Close() + if err != nil { + return nil, err + } + } + + if b.actions, err = getActions( + b.meta.Name, + func(file string) (io.ReadCloser, error) { + return zipOpenFile(zipr, file) + }, + func(err error) bool { + _, ok := err.(*noCharmArchiveFile) + return ok + }, + ); err != nil { + return nil, err + } + + reader, err = zipOpenFile(zipr, "revision") + if err != nil { + if _, ok := err.(*noCharmArchiveFile); !ok { + return nil, err + } + } else { + _, err = fmt.Fscan(reader, &b.revision) + if err != nil { + return nil, errors.New("invalid revision file") + } + } + + reader, err = zipOpenFile(zipr, "lxd-profile.yaml") + if _, ok := err.(*noCharmArchiveFile); ok { + b.lxdProfile = NewLXDProfile() + } else if err != nil { + return nil, err + } else { + b.lxdProfile, err = ReadLXDProfile(reader) + _ = reader.Close() + if err != nil { + return nil, err + } + } + + reader, err = zipOpenFile(zipr, "version") + if err != nil { + if _, ok := err.(*noCharmArchiveFile); !ok { + return nil, err + } + } else { + b.version, err = ReadVersion(reader) + _ = reader.Close() + if err != nil { + return nil, err + } + } + + return b, nil +} + +type fileOpener func(string) (io.ReadCloser, error) + +func getActions(charmName string, open fileOpener, isNotFound func(error) bool) (actions *Actions, err error) { + reader, err := open("actions.yaml") + if err == nil { + defer reader.Close() + return ReadActionsYaml(charmName, reader) + } else if !isNotFound(err) { + return nil, err + } + return NewActions(), nil +} + +func zipOpenFile(zipr *zipReadCloser, path string) (rc io.ReadCloser, err error) { + for _, fh := range zipr.File { + if fh.Name == path { + return fh.Open() + } + } + return nil, &noCharmArchiveFile{path} +} + +type noCharmArchiveFile struct { + path string +} + +func (err noCharmArchiveFile) Error() string { + return fmt.Sprintf("archive file %q not found", err.path) +} + +type zipReadCloser struct { + io.Closer + *zip.Reader +} + +// zipOpener holds the information needed to open a zip +// file. +type zipOpener interface { + openZip() (*zipReadCloser, error) +} + +// newZipOpenerFromPath returns a zipOpener that can be +// used to read the archive from the given path. +func newZipOpenerFromPath(path string) zipOpener { + return &zipPathOpener{path: path} +} + +// newZipOpenerFromReader returns a zipOpener that can be +// used to read the archive from the given ReaderAt +// holding the given number of bytes. +func newZipOpenerFromReader(r io.ReaderAt, size int64) zipOpener { + return &zipReaderOpener{ + r: r, + size: size, + } +} + +type zipPathOpener struct { + path string +} + +func (zo *zipPathOpener) openZip() (*zipReadCloser, error) { + f, err := os.Open(zo.path) + if err != nil { + return nil, err + } + fi, err := f.Stat() + if err != nil { + f.Close() + return nil, err + } + r, err := zip.NewReader(f, fi.Size()) + if err != nil { + f.Close() + return nil, err + } + return &zipReadCloser{Closer: f, Reader: r}, nil +} + +type zipReaderOpener struct { + r io.ReaderAt + size int64 +} + +func (zo *zipReaderOpener) openZip() (*zipReadCloser, error) { + r, err := zip.NewReader(zo.r, zo.size) + if err != nil { + return nil, err + } + return &zipReadCloser{Closer: ioutil.NopCloser(nil), Reader: r}, nil +} + +// ArchiveMembers returns a set of the charm's contents. +func (a *CharmArchive) ArchiveMembers() (set.Strings, error) { + zipr, err := a.zopen.openZip() + if err != nil { + return set.NewStrings(), err + } + defer zipr.Close() + paths, err := ziputil.Find(zipr.Reader, "*") + if err != nil { + return set.NewStrings(), err + } + manifest := set.NewStrings(paths...) + // We always write out a revision file, even if there isn't one in the + // archive; and we always strip ".", because that's sometimes not present. + manifest.Add("revision") + manifest.Remove(".") + return manifest, nil +} + +// ExpandTo expands the charm archive into dir, creating it if necessary. +// If any errors occur during the expansion procedure, the process will +// abort. +func (a *CharmArchive) ExpandTo(dir string) error { + zipr, err := a.zopen.openZip() + if err != nil { + return err + } + defer zipr.Close() + if err := ziputil.ExtractAll(zipr.Reader, dir); err != nil { + return err + } + hooksDir := filepath.Join(dir, "hooks") + fixHook := fixHookFunc(hooksDir, a.meta.Hooks()) + if err := filepath.Walk(hooksDir, fixHook); err != nil { + if !os.IsNotExist(err) { + return err + } + } + revFile, err := os.Create(filepath.Join(dir, "revision")) + if err != nil { + return err + } + if _, err := revFile.Write([]byte(strconv.Itoa(a.revision))); err != nil { + return err + } + if err := revFile.Sync(); err != nil { + return err + } + if err := revFile.Close(); err != nil { + return err + } + return nil +} + +// fixHookFunc returns a WalkFunc that makes sure hooks are owner-executable. +func fixHookFunc(hooksDir string, hookNames map[string]bool) filepath.WalkFunc { + return func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + mode := info.Mode() + if path != hooksDir && mode.IsDir() { + return filepath.SkipDir + } + if name := filepath.Base(path); hookNames[name] { + if mode&0100 == 0 { + return os.Chmod(path, mode|0100) + } + } + return nil + } +} diff --git a/internal/charm/charmarchive_test.go b/internal/charm/charmarchive_test.go new file mode 100644 index 00000000000..770c7eeaf26 --- /dev/null +++ b/internal/charm/charmarchive_test.go @@ -0,0 +1,455 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "archive/zip" + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "syscall" + + "github.com/juju/collections/set" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/internal/charm" +) + +type CharmArchiveSuite struct { + testing.IsolationSuite + archivePath string +} + +var _ = gc.Suite(&CharmArchiveSuite{}) + +func (s *CharmArchiveSuite) SetUpSuite(c *gc.C) { + s.IsolationSuite.SetUpSuite(c) + s.archivePath = archivePath(c, readCharmDir(c, "dummy")) +} + +var dummyArchiveMembersCommon = []string{ + "config.yaml", + "empty", + "empty/.gitkeep", + "hooks", + "hooks/install", + "lxd-profile.yaml", + "manifest.yaml", + "metadata.yaml", + "revision", + "src", + "src/hello.c", + ".notignored", +} + +var dummyArchiveMembers = append(dummyArchiveMembersCommon, "actions.yaml") +var dummyArchiveMembersActions = append(dummyArchiveMembersCommon, []string{ + "actions.yaml", + "actions/snapshot", + "actions", +}...) + +func (s *CharmArchiveSuite) TestReadCharmArchive(c *gc.C) { + archive, err := charm.ReadCharmArchive(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + checkDummy(c, archive, s.archivePath) +} + +func (s *CharmArchiveSuite) TestReadCharmArchiveWithoutConfig(c *gc.C) { + // Technically varnish has no config AND no actions. + // Perhaps we should make this more orthogonal? + path := archivePath(c, readCharmDir(c, "varnish")) + archive, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + + // A lacking config.yaml file still causes a proper + // Config value to be returned. + c.Assert(archive.Config().Options, gc.HasLen, 0) +} + +func (s *CharmArchiveSuite) TestReadCharmArchiveManifest(c *gc.C) { + path := archivePath(c, readCharmDir(c, "dummy")) + dir, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + + c.Assert(dir.Manifest().Bases, gc.DeepEquals, []charm.Base{{ + Name: "ubuntu", + Channel: charm.Channel{ + + Track: "18.04", + Risk: "stable", + }, + }, { + Name: "ubuntu", + Channel: charm.Channel{ + Track: "20.04", + Risk: "stable", + }, + }}) +} + +func (s *CharmArchiveSuite) TestReadCharmArchiveWithoutManifest(c *gc.C) { + path := archivePath(c, readCharmDir(c, "mysql")) + dir, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Manifest(), gc.IsNil) +} + +func (s *CharmArchiveSuite) TestReadCharmArchiveWithoutActions(c *gc.C) { + // Wordpress has config but no actions. + path := archivePath(c, readCharmDir(c, "wordpress")) + archive, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + + // A lacking actions.yaml file still causes a proper + // Actions value to be returned. + c.Assert(archive.Actions().ActionSpecs, gc.HasLen, 0) +} + +func (s *CharmArchiveSuite) TestReadCharmArchiveWithActions(c *gc.C) { + path := archivePath(c, readCharmDir(c, "dummy-actions")) + archive, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(archive.Actions().ActionSpecs, gc.HasLen, 1) +} + +func (s *CharmDirSuite) TestReadCharmArchiveWithJujuActions(c *gc.C) { + path := archivePath(c, readCharmDir(c, "juju-charm")) + archive, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(archive.Actions().ActionSpecs, gc.HasLen, 1) +} + +func (s *CharmArchiveSuite) TestReadCharmArchiveBytes(c *gc.C) { + data, err := ioutil.ReadFile(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadCharmArchiveBytes(data) + c.Assert(err, jc.ErrorIsNil) + checkDummy(c, archive, "") +} + +func (s *CharmArchiveSuite) TestReadCharmArchiveFromReader(c *gc.C) { + f, err := os.Open(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + defer func() { _ = f.Close() }() + info, err := f.Stat() + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadCharmArchiveFromReader(f, info.Size()) + c.Assert(err, jc.ErrorIsNil) + checkDummy(c, archive, "") +} + +func (s *CharmArchiveSuite) TestArchiveMembers(c *gc.C) { + archive, err := charm.ReadCharmArchive(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + manifest, err := archive.ArchiveMembers() + c.Assert(err, jc.ErrorIsNil) + c.Assert(manifest, jc.DeepEquals, set.NewStrings(dummyArchiveMembers...)) +} + +func (s *CharmArchiveSuite) TestArchiveMembersActions(c *gc.C) { + path := archivePath(c, readCharmDir(c, "dummy-actions")) + archive, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + manifest, err := archive.ArchiveMembers() + c.Assert(err, jc.ErrorIsNil) + c.Assert(manifest, jc.DeepEquals, set.NewStrings(dummyArchiveMembersActions...)) +} + +func (s *CharmArchiveSuite) TestArchiveMembersNoRevision(c *gc.C) { + archive, err := charm.ReadCharmArchive(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + dirPath := c.MkDir() + err = archive.ExpandTo(dirPath) + c.Assert(err, jc.ErrorIsNil) + err = os.Remove(filepath.Join(dirPath, "revision")) + c.Assert(err, jc.ErrorIsNil) + + archive = extCharmArchiveDir(c, dirPath) + manifest, err := archive.ArchiveMembers() + c.Assert(err, jc.ErrorIsNil) + c.Assert(manifest, gc.DeepEquals, set.NewStrings(dummyArchiveMembers...)) +} + +func (s *CharmArchiveSuite) TestArchiveMembersSymlink(c *gc.C) { + srcPath := cloneDir(c, charmDirPath(c, "dummy")) + if err := os.Symlink("../target", filepath.Join(srcPath, "hooks/symlink")); err != nil { + c.Skip("cannot symlink") + } + expected := append([]string{"hooks/symlink"}, dummyArchiveMembers...) + + archive := archiveDir(c, srcPath) + manifest, err := archive.ArchiveMembers() + c.Assert(err, jc.ErrorIsNil) + c.Assert(manifest, gc.DeepEquals, set.NewStrings(expected...)) +} + +func (s *CharmArchiveSuite) TestExpandTo(c *gc.C) { + archive, err := charm.ReadCharmArchive(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + + path := filepath.Join(c.MkDir(), "charm") + err = archive.ExpandTo(path) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + checkDummy(c, dir, path) +} + +func (s *CharmArchiveSuite) TestReadCharmArchiveWithVersion(c *gc.C) { + clonedPath := cloneDir(c, charmDirPath(c, "versioned")) + _, err := os.Create(filepath.Join(clonedPath, ".git")) + c.Assert(err, jc.ErrorIsNil) + + // NOTE(achilleasa) Initially, I tried using PatchExecutableAsEchoArgs + // but it doesn't work as expected on my bionic box so I reverted to + // the following less elegant approach to stubbing git output. + var gitOutput string + switch runtime.GOOS { + case "windows": + gitOutput = "@echo off\r\necho c0ffee" + default: + gitOutput = "#!/bin/bash -norc\necho c0ffee" + } + testing.PatchExecutable(c, s, "git", gitOutput) + + // Read cloned path and archive it; the archive should now include + // the version fetched from our mocked call to git + cd, err := charm.ReadCharmDir(clonedPath) + c.Assert(err, jc.ErrorIsNil) + path := archivePath(c, cd) + + // Read back the archive and verify the correct version + archive, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(archive.Version(), gc.Equals, "c0ffee") +} + +func (s *CharmArchiveSuite) prepareCharmArchive(c *gc.C, charmDir *charm.CharmDir, archivePath string) { + file, err := os.Create(archivePath) + c.Assert(err, jc.ErrorIsNil) + defer file.Close() + zipw := zip.NewWriter(file) + defer zipw.Close() + + h := &zip.FileHeader{Name: "revision"} + h.SetMode(syscall.S_IFREG | 0644) + w, err := zipw.CreateHeader(h) + c.Assert(err, jc.ErrorIsNil) + _, err = w.Write([]byte(strconv.Itoa(charmDir.Revision()))) + c.Assert(err, jc.ErrorIsNil) + + h = &zip.FileHeader{Name: "metadata.yaml", Method: zip.Deflate} + h.SetMode(0644) + w, err = zipw.CreateHeader(h) + c.Assert(err, jc.ErrorIsNil) + data, err := yaml.Marshal(charmDir.Meta()) + c.Assert(err, jc.ErrorIsNil) + _, err = w.Write(data) + c.Assert(err, jc.ErrorIsNil) + + for name := range charmDir.Meta().Hooks() { + hookName := filepath.Join("hooks", name) + h = &zip.FileHeader{ + Name: hookName, + Method: zip.Deflate, + } + // Force it non-executable + h.SetMode(0644) + w, err := zipw.CreateHeader(h) + c.Assert(err, jc.ErrorIsNil) + _, err = w.Write([]byte("not important")) + c.Assert(err, jc.ErrorIsNil) + } +} + +func (s *CharmArchiveSuite) TestExpandToSetsHooksExecutable(c *gc.C) { + charmDir, err := charm.ReadCharmDir(cloneDir(c, charmDirPath(c, "all-hooks"))) + c.Assert(err, jc.ErrorIsNil) + // CharmArchive manually, so we can check ExpandTo(), unaffected + // by ArchiveTo()'s behavior + archivePath := filepath.Join(c.MkDir(), "archive.charm") + s.prepareCharmArchive(c, charmDir, archivePath) + archive, err := charm.ReadCharmArchive(archivePath) + c.Assert(err, jc.ErrorIsNil) + + path := filepath.Join(c.MkDir(), "charm") + err = archive.ExpandTo(path) + c.Assert(err, jc.ErrorIsNil) + + _, err = charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + + for name := range archive.Meta().Hooks() { + hookName := string(name) + info, err := os.Stat(filepath.Join(path, "hooks", hookName)) + c.Assert(err, jc.ErrorIsNil) + perm := info.Mode() & 0777 + c.Assert(perm&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", hookName)) + } +} + +func (s *CharmArchiveSuite) TestCharmArchiveFileModes(c *gc.C) { + // Apply subtler mode differences than can be expressed in Bazaar. + srcPath := cloneDir(c, charmDirPath(c, "dummy")) + modes := []struct { + path string + mode os.FileMode + }{ + {"hooks/install", 0751}, + {"empty", 0750}, + {"src/hello.c", 0614}, + } + for _, m := range modes { + err := os.Chmod(filepath.Join(srcPath, m.path), m.mode) + c.Assert(err, jc.ErrorIsNil) + } + var haveSymlinks = true + if err := os.Symlink("../target", filepath.Join(srcPath, "hooks/symlink")); err != nil { + haveSymlinks = false + } + + // CharmArchive and extract the charm to a new directory. + archive := archiveDir(c, srcPath) + path := c.MkDir() + err := archive.ExpandTo(path) + c.Assert(err, jc.ErrorIsNil) + + // Check sensible file modes once round-tripped. + info, err := os.Stat(filepath.Join(path, "src", "hello.c")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0644)) + c.Assert(info.Mode()&os.ModeType, gc.Equals, os.FileMode(0)) + + info, err = os.Stat(filepath.Join(path, "hooks", "install")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0755)) + c.Assert(info.Mode()&os.ModeType, gc.Equals, os.FileMode(0)) + + info, err = os.Stat(filepath.Join(path, "empty")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(info.Mode()&0777, gc.Equals, os.FileMode(0755)) + + if haveSymlinks { + target, err := os.Readlink(filepath.Join(path, "hooks", "symlink")) + c.Assert(err, jc.ErrorIsNil) + c.Assert(target, gc.Equals, "../target") + } +} + +func (s *CharmArchiveSuite) TestCharmArchiveRevisionFile(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + revPath := filepath.Join(charmDir, "revision") + + // Missing revision file + err := os.Remove(revPath) + c.Assert(err, jc.ErrorIsNil) + + archive := extCharmArchiveDir(c, charmDir) + c.Assert(archive.Revision(), gc.Equals, 0) + + // Missing revision file with obsolete old revision in metadata; + // the revision is ignored. + file, err := os.OpenFile(filepath.Join(charmDir, "metadata.yaml"), os.O_WRONLY|os.O_APPEND, 0) + c.Assert(err, jc.ErrorIsNil) + _, err = file.Write([]byte("\nrevision: 1234\n")) + c.Assert(err, jc.ErrorIsNil) + + archive = extCharmArchiveDir(c, charmDir) + c.Assert(archive.Revision(), gc.Equals, 0) + + // Revision file with bad content + err = ioutil.WriteFile(revPath, []byte("garbage"), 0666) + c.Assert(err, jc.ErrorIsNil) + + path := extCharmArchiveDirPath(c, charmDir) + archive, err = charm.ReadCharmArchive(path) + c.Assert(err, gc.ErrorMatches, "invalid revision file") + c.Assert(archive, gc.IsNil) +} + +func (s *CharmArchiveSuite) TestCharmArchiveSetRevision(c *gc.C) { + archive, err := charm.ReadCharmArchive(s.archivePath) + c.Assert(err, jc.ErrorIsNil) + + c.Assert(archive.Revision(), gc.Equals, 1) + archive.SetRevision(42) + c.Assert(archive.Revision(), gc.Equals, 42) + + path := filepath.Join(c.MkDir(), "charm") + err = archive.ExpandTo(path) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Revision(), gc.Equals, 42) +} + +func (s *CharmArchiveSuite) TestExpandToWithBadLink(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + badLink := filepath.Join(charmDir, "hooks", "badlink") + + // Symlink targeting a path outside of the charm. + err := os.Symlink("../../target", badLink) + c.Assert(err, jc.ErrorIsNil) + + archive := extCharmArchiveDir(c, charmDir) + c.Assert(err, jc.ErrorIsNil) + + path := filepath.Join(c.MkDir(), "charm") + err = archive.ExpandTo(path) + c.Assert(err, gc.ErrorMatches, `cannot extract "hooks/badlink": symlink "../../target" leads out of scope`) + + // Symlink targeting an absolute path. + os.Remove(badLink) + err = os.Symlink("/target", badLink) + c.Assert(err, jc.ErrorIsNil) + + archive = extCharmArchiveDir(c, charmDir) + c.Assert(err, jc.ErrorIsNil) + + path = filepath.Join(c.MkDir(), "charm") + err = archive.ExpandTo(path) + c.Assert(err, gc.ErrorMatches, `cannot extract "hooks/badlink": symlink "/target" is absolute`) +} + +func extCharmArchiveDirPath(c *gc.C, dirpath string) string { + path := filepath.Join(c.MkDir(), "archive.charm") + cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("cd %s; zip --fifo --symlinks -r %s .", dirpath, path)) + output, err := cmd.CombinedOutput() + c.Assert(err, gc.IsNil, gc.Commentf("Command output: %s", output)) + return path +} + +func extCharmArchiveDir(c *gc.C, dirpath string) *charm.CharmArchive { + path := extCharmArchiveDirPath(c, dirpath) + archive, err := charm.ReadCharmArchive(path) + c.Assert(err, jc.ErrorIsNil) + return archive +} + +func archiveDir(c *gc.C, dirpath string) *charm.CharmArchive { + dir, err := charm.ReadCharmDir(dirpath) + c.Assert(err, jc.ErrorIsNil) + + buf := new(bytes.Buffer) + err = dir.ArchiveTo(buf) + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadCharmArchiveBytes(buf.Bytes()) + c.Assert(err, jc.ErrorIsNil) + + return archive +} diff --git a/internal/charm/charmbase.go b/internal/charm/charmbase.go new file mode 100644 index 00000000000..c881a7266d4 --- /dev/null +++ b/internal/charm/charmbase.go @@ -0,0 +1,65 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charm + +// charmBase implements the Charm interface with commonality between +// a charm archive and directory. +type charmBase struct { + meta *Meta + config *Config + actions *Actions + lxdProfile *LXDProfile + manifest *Manifest + revision int + version string +} + +// Revision returns the revision number for the charm +// expanded in dir. +func (c *charmBase) Revision() int { + return c.revision +} + +// Version returns the VCS version representing the version file from archive. +func (c *charmBase) Version() string { + return c.version +} + +// Meta returns the Meta representing the metadata.yaml file +// for the charm expanded in dir. +func (c *charmBase) Meta() *Meta { + return c.meta +} + +// Config returns the Config representing the config.yaml file +// for the charm expanded in dir. +func (c *charmBase) Config() *Config { + return c.config +} + +// Actions returns the Actions representing the actions.yaml file +// for the charm expanded in dir. +func (c *charmBase) Actions() *Actions { + return c.actions +} + +// LXDProfile returns the LXDProfile representing the lxd-profile.yaml file +// for the charm expanded in dir. +func (c *charmBase) LXDProfile() *LXDProfile { + return c.lxdProfile +} + +// Manifest returns the Manifest representing the manifest.yaml file +// for the charm expanded in dir. +func (c *charmBase) Manifest() *Manifest { + return c.manifest +} + +// SetRevision changes the charm revision number. This affects +// the revision reported by Revision and the revision of the +// charm created. +// The revision file in the charm directory is not modified. +func (c *charmBase) SetRevision(revision int) { + c.revision = revision +} diff --git a/internal/charm/charmdir.go b/internal/charm/charmdir.go new file mode 100644 index 00000000000..09828dc730b --- /dev/null +++ b/internal/charm/charmdir.go @@ -0,0 +1,563 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "archive/zip" + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "github.com/juju/errors" + + "github.com/juju/juju/core/logger" + internallogger "github.com/juju/juju/internal/logger" +) + +// defaultJujuIgnore contains jujuignore directives for excluding VCS- and +// build-related directories when archiving. The following set of directives +// will be prepended to the contents of the charm's .jujuignore file if one is +// provided. +// +// NOTE: writeArchive auto-generates its own revision and version files so they +// need to be excluded here to prevent anyone from overriding their contents by +// adding files with the same name to their charm repo. +var defaultJujuIgnore = ` +.git +.svn +.hg +.bzr +.tox + +/build/ +/revision +/version + +.jujuignore +` + +// ReadOption represents an option that can be applied to a CharmDir. +type ReadOption func(*readOptions) + +// WithLogger sets the logger for the CharmDir. +func WithLogger(logger logger.Logger) ReadOption { + return func(opts *readOptions) { + opts.logger = logger + } +} + +type readOptions struct { + logger logger.Logger +} + +func newReadOptions(options []ReadOption) *readOptions { + opts := &readOptions{ + logger: internallogger.GetLogger("juju.charm"), + } + for _, option := range options { + option(opts) + } + return opts +} + +// CharmDir encapsulates access to data and operations +// on a charm directory. +type CharmDir struct { + *charmBase + + Path string + logger logger.Logger +} + +// Trick to ensure *CharmDir implements the Charm interface. +var _ Charm = (*CharmDir)(nil) + +// IsCharmDir report whether the path is likely to represent +// a charm, even it may be incomplete. +func IsCharmDir(path string) bool { + dir := &CharmDir{Path: path} + _, err := os.Stat(dir.join("metadata.yaml")) + return err == nil +} + +// ReadCharmDir returns a CharmDir representing an expanded charm directory. +func ReadCharmDir(path string, options ...ReadOption) (*CharmDir, error) { + opts := newReadOptions(options) + + b := &CharmDir{ + Path: path, + charmBase: &charmBase{}, + logger: opts.logger, + } + reader, err := os.Open(b.join("metadata.yaml")) + if err != nil { + return nil, errors.Annotatef(err, `reading "metadata.yaml" file`) + } + b.meta, err = ReadMeta(reader) + _ = reader.Close() + if err != nil { + return nil, errors.Annotatef(err, `parsing "metadata.yaml" file`) + } + + // Try to read the optional manifest.yaml, it's required to determine if + // this charm is v1 or not. + reader, err = os.Open(b.join("manifest.yaml")) + if _, ok := err.(*os.PathError); ok { + b.manifest = nil + } else if err != nil { + return nil, errors.Annotatef(err, `reading "manifest.yaml" file`) + } else { + b.manifest, err = ReadManifest(reader) + _ = reader.Close() + if err != nil { + return nil, errors.Annotatef(err, `parsing "manifest.yaml" file`) + } + } + + reader, err = os.Open(b.join("config.yaml")) + if _, ok := err.(*os.PathError); ok { + b.config = NewConfig() + } else if err != nil { + return nil, errors.Annotatef(err, `reading "config.yaml" file`) + } else { + b.config, err = ReadConfig(reader) + _ = reader.Close() + if err != nil { + return nil, errors.Annotatef(err, `parsing "config.yaml" file`) + } + } + + if b.actions, err = getActions( + b.meta.Name, + func(file string) (io.ReadCloser, error) { + return os.Open(b.join(file)) + }, + func(err error) bool { + _, ok := err.(*os.PathError) + return ok + }, + ); err != nil { + return nil, err + } + + if reader, err = os.Open(b.join("revision")); err == nil { + _, err = fmt.Fscan(reader, &b.revision) + _ = reader.Close() + if err != nil { + return nil, errors.New("invalid revision file") + } + } + + reader, err = os.Open(b.join("lxd-profile.yaml")) + if _, ok := err.(*os.PathError); ok { + b.lxdProfile = NewLXDProfile() + } else if err != nil { + return nil, errors.Annotatef(err, `reading "lxd-profile.yaml" file`) + } else { + b.lxdProfile, err = ReadLXDProfile(reader) + _ = reader.Close() + if err != nil { + return nil, errors.Annotatef(err, `parsing "lxd-profile.yaml" file`) + } + } + + reader, err = os.Open(b.join("version")) + if err != nil { + if _, ok := err.(*os.PathError); !ok { + return nil, errors.Annotatef(err, `reading "version" file`) + } + } else { + b.version, err = ReadVersion(reader) + _ = reader.Close() + if err != nil { + return nil, errors.Annotatef(err, `parsing "version" file`) + } + } + + return b, nil +} + +// buildIgnoreRules parses the contents of the charm's .jujuignore file and +// compiles a set of rules that are used to decide which files should be +// archived. +func (dir *CharmDir) buildIgnoreRules() (ignoreRuleset, error) { + // Start with a set of sane defaults to ensure backwards-compatibility + // for charms that do not use a .jujuignore file. + rules, err := newIgnoreRuleset(strings.NewReader(defaultJujuIgnore)) + if err != nil { + return nil, err + } + + pathToJujuignore := dir.join(".jujuignore") + if _, err := os.Stat(pathToJujuignore); err == nil { + file, err := os.Open(dir.join(".jujuignore")) + if err != nil { + return nil, errors.Annotatef(err, `reading ".jujuignore" file`) + } + defer func() { _ = file.Close() }() + + jujuignoreRules, err := newIgnoreRuleset(file) + if err != nil { + return nil, errors.Annotate(err, `parsing ".jujuignore" file`) + } + + rules = append(rules, jujuignoreRules...) + } + + return rules, nil +} + +// join builds a path rooted at the charm's expanded directory +// path and the extra path components provided. +func (dir *CharmDir) join(parts ...string) string { + parts = append([]string{dir.Path}, parts...) + return filepath.Join(parts...) +} + +// SetDiskRevision does the same as SetRevision but also changes +// the revision file in the charm directory. +func (dir *CharmDir) SetDiskRevision(revision int) error { + dir.SetRevision(revision) + file, err := os.OpenFile(dir.join("revision"), os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + _, err = file.Write([]byte(strconv.Itoa(revision))) + file.Close() + return err +} + +// resolveSymlinkedRoot returns the target destination of a +// charm root directory if the root directory is a symlink. +func resolveSymlinkedRoot(rootPath string) (string, error) { + info, err := os.Lstat(rootPath) + if err == nil && info.Mode()&os.ModeSymlink != 0 { + rootPath, err = filepath.EvalSymlinks(rootPath) + if err != nil { + return "", fmt.Errorf("cannot read path symlink at %q: %v", rootPath, err) + } + } + return rootPath, nil +} + +// ArchiveTo creates a charm file from the charm expanded in dir. +// By convention a charm archive should have a ".charm" suffix. +func (dir *CharmDir) ArchiveTo(w io.Writer) error { + ignoreRules, err := dir.buildIgnoreRules() + if err != nil { + return err + } + // We update the version to make sure we don't lag behind + dir.version, _, err = dir.MaybeGenerateVersionString() + if err != nil { + // We don't want to stop, even if the version cannot be generated + dir.logger.Warningf("trying to generate version string: %v", err) + } + + return writeArchive(w, dir.Path, dir.revision, dir.version, dir.Meta().Hooks(), ignoreRules, dir.logger) +} + +func writeArchive( + w io.Writer, + path string, + revision int, + versionString string, + hooks map[string]bool, + ignoreRules ignoreRuleset, + logger logger.Logger, +) error { + zipw := zip.NewWriter(w) + defer zipw.Close() + + // The root directory may be symlinked elsewhere so + // resolve that before creating the zip. + rootPath, err := resolveSymlinkedRoot(path) + if err != nil { + return err + } + zp := zipPacker{ + Writer: zipw, + root: rootPath, + hooks: hooks, + ignoreRules: ignoreRules, + logger: logger, + } + if revision != -1 { + zp.AddFile("revision", strconv.Itoa(revision)) + } + if versionString != "" { + zp.AddFile("version", versionString) + } + return filepath.Walk(rootPath, zp.WalkFunc()) +} + +type zipPacker struct { + *zip.Writer + root string + hooks map[string]bool + ignoreRules ignoreRuleset + logger logger.Logger +} + +func (zp *zipPacker) WalkFunc() filepath.WalkFunc { + return func(path string, fi os.FileInfo, err error) error { + return zp.visit(path, fi, err) + } +} + +func (zp *zipPacker) AddFile(filename string, value string) error { + h := &zip.FileHeader{Name: filename} + h.SetMode(syscall.S_IFREG | 0644) + w, err := zp.CreateHeader(h) + if err == nil { + _, err = w.Write([]byte(value)) + } + return err +} + +func (zp *zipPacker) visit(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + relpath, err := filepath.Rel(zp.root, path) + if err != nil { + return err + } + + // Replace any Windows path separators with "/". + // zip file spec 4.4.17.1 says that separators are always "/" even on Windows. + relpath = filepath.ToSlash(relpath) + + // Check if this file or dir needs to be ignored + if zp.ignoreRules.Match(relpath, fi.IsDir()) { + if fi.IsDir() { + return filepath.SkipDir + } + + return nil + } + + method := zip.Deflate + if fi.IsDir() { + relpath += "/" + method = zip.Store + } + + mode := fi.Mode() + if err := checkFileType(relpath, mode); err != nil { + return err + } + if mode&os.ModeSymlink != 0 { + method = zip.Store + } + h := &zip.FileHeader{ + Name: relpath, + Method: method, + } + + perm := os.FileMode(0644) + if mode&os.ModeSymlink != 0 { + perm = 0777 + } else if mode&0100 != 0 { + perm = 0755 + } + if filepath.Dir(relpath) == "hooks" { + hookName := filepath.Base(relpath) + if _, ok := zp.hooks[hookName]; ok && !fi.IsDir() && mode&0100 == 0 { + zp.logger.Warningf("making %q executable in charm", path) + perm = perm | 0100 + } + } + h.SetMode(mode&^0777 | perm) + + w, err := zp.CreateHeader(h) + if err != nil || fi.IsDir() { + return err + } + var data []byte + if mode&os.ModeSymlink != 0 { + target, err := os.Readlink(path) + if err != nil { + return err + } + if err := checkSymlinkTarget(relpath, target); err != nil { + return err + } + data = []byte(target) + if _, err := w.Write(data); err != nil { + return err + } + } + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(w, file) + return err +} + +func checkSymlinkTarget(symlink, target string) error { + if filepath.IsAbs(target) { + return fmt.Errorf("symlink %q is absolute: %q", symlink, target) + } + p := filepath.Join(filepath.Dir(symlink), target) + if p == ".." || strings.HasPrefix(p, "../") { + return fmt.Errorf("symlink %q links out of charm: %q", symlink, target) + } + return nil +} + +func checkFileType(path string, mode os.FileMode) error { + e := "file has an unknown type: %q" + switch mode & os.ModeType { + case os.ModeDir, os.ModeSymlink, 0: + return nil + case os.ModeNamedPipe: + e = "file is a named pipe: %q" + case os.ModeSocket: + e = "file is a socket: %q" + case os.ModeDevice: + e = "file is a device: %q" + } + return fmt.Errorf(e, path) +} + +type typeCheckerFunc = func(ctx context.Context, charmPath string, CancelFunc func(), logger logger.Logger) bool + +type vcsCMD struct { + vcsType string + args []string + usesTypeCheck typeCheckerFunc +} + +func (v *vcsCMD) commonErrHandler(err error, charmPath string) error { + return errors.Errorf("%q version string generation failed : "+ + "%v\nThis means that the charm version won't show in juju status. Charm path %q", v.vcsType, err, charmPath) +} + +// usesGit first check checks for the easy case of the current charmdir has a +// git folder. +// There can be cases when the charmdir actually uses git and is just a subdir, +// hence the below check +func usesGit(ctx context.Context, charmPath string, cancelFunc func(), logger logger.Logger) bool { + defer cancelFunc() + if _, err := os.Stat(filepath.Join(charmPath, ".git")); err == nil { + return true + } + args := []string{"rev-parse", "--is-inside-work-tree"} + execCmd := exec.CommandContext(ctx, "git", args...) + execCmd.Dir = charmPath + + _, err := execCmd.Output() + + if ctx.Err() == context.DeadlineExceeded { + logger.Debugf("git command timed out for charm in path: %q", charmPath) + return false + } + + if err == nil { + return true + } + return false +} + +func usesBzr(ctx context.Context, charmPath string, cancelFunc func(), logger logger.Logger) bool { + defer cancelFunc() + if _, err := os.Stat(filepath.Join(charmPath, ".bzr")); err == nil { + return true + } + return false +} + +func usesHg(ctx context.Context, charmPath string, cancelFunc func(), logger logger.Logger) bool { + defer cancelFunc() + if _, err := os.Stat(filepath.Join(charmPath, ".hg")); err == nil { + return true + } + return false +} + +// VersionFileVersionType holds the type of the versioned file type, either +// git, hg, bzr or a raw version file. +const versionFileVersionType = "versionFile" + +// MaybeGenerateVersionString generates charm version string. +// We want to know whether parent folders use one of these vcs, that's why we +// try to execute each one of them +// The second return value is the detected vcs type. +func (dir *CharmDir) MaybeGenerateVersionString() (string, string, error) { + // vcsStrategies is the strategies to use to access the version file content. + vcsStrategies := map[string]vcsCMD{ + "hg": { + vcsType: "hg", + args: []string{"id", "-n"}, + usesTypeCheck: usesHg, + }, + "git": { + vcsType: "git", + args: []string{"describe", "--dirty", "--always"}, + usesTypeCheck: usesGit, + }, + "bzr": { + vcsType: "bzr", + args: []string{"version-info"}, + usesTypeCheck: usesBzr, + }, + } + + // Nowadays most vcs used are git, we want to make sure that git is the first one we test + vcsOrder := [...]string{"git", "hg", "bzr"} + cmdWaitTime := 2 * time.Second + + absPath := dir.Path + if !filepath.IsAbs(absPath) { + var err error + absPath, err = filepath.Abs(dir.Path) + if err != nil { + return "", "", errors.Annotatef(err, "failed resolving relative path %q", dir.Path) + } + } + + for _, vcsType := range vcsOrder { + vcsCmd := vcsStrategies[vcsType] + ctx, cancel := context.WithTimeout(context.Background(), cmdWaitTime) + if vcsCmd.usesTypeCheck(ctx, dir.Path, cancel, dir.logger) { + cmd := exec.Command(vcsCmd.vcsType, vcsCmd.args...) + // We need to make sure that the working directory will be the one we execute the commands from. + cmd.Dir = dir.Path + // Version string value is written to stdout if successful. + out, err := cmd.Output() + if err != nil { + // We had an error but we still know that we use a vcs, hence we can stop here and handle it. + return "", vcsType, vcsCmd.commonErrHandler(err, absPath) + } + output := strings.TrimSuffix(string(out), "\n") + return output, vcsType, nil + } + } + + // If all strategies fail we fallback to check the version below + if file, err := os.Open(dir.join("version")); err == nil { + dir.logger.Debugf("charm is not in version control, but uses a version file, charm path %q", absPath) + ver, err := ReadVersion(file) + file.Close() + if err != nil { + return "", versionFileVersionType, err + } + return ver, versionFileVersionType, nil + } + dir.logger.Infof("charm is not versioned, charm path %q", absPath) + return "", "", nil +} diff --git a/internal/charm/charmdir_test.go b/internal/charm/charmdir_test.go new file mode 100644 index 00000000000..83d194d0027 --- /dev/null +++ b/internal/charm/charmdir_test.go @@ -0,0 +1,731 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "archive/zip" + "bytes" + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/juju/collections/set" + "github.com/juju/loggo/v2" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + "github.com/juju/utils/v4" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" + internallogger "github.com/juju/juju/internal/logger" + loggertesting "github.com/juju/juju/internal/logger/testing" +) + +type CharmDirSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&CharmDirSuite{}) + +func (s *CharmDirSuite) TestIsCharmDirGoodCharm(c *gc.C) { + path := charmDirPath(c, "dummy") + c.Assert(charm.IsCharmDir(path), jc.IsTrue) +} + +func (s *CharmDirSuite) TestIsCharmDirBundle(c *gc.C) { + path := bundleDirPath(c, "wordpress-simple") + c.Assert(charm.IsCharmDir(path), jc.IsFalse) +} + +func (s *CharmDirSuite) TestIsCharmDirNoMetadataYaml(c *gc.C) { + path := charmDirPath(c, "bad") + c.Assert(charm.IsCharmDir(path), jc.IsFalse) +} + +func (s *CharmDirSuite) TestReadCharmDir(c *gc.C) { + path := charmDirPath(c, "dummy") + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + checkDummy(c, dir, path) +} + +func (s *CharmDirSuite) TestReadCharmDirWithoutConfig(c *gc.C) { + path := charmDirPath(c, "varnish") + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + + // A lacking config.yaml file still causes a proper + // Config value to be returned. + c.Assert(dir.Config().Options, gc.HasLen, 0) +} + +func (s *CharmDirSuite) TestReadCharmDirWithoutActions(c *gc.C) { + path := charmDirPath(c, "wordpress") + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + + // A lacking actions.yaml file still causes a proper + // Actions value to be returned. + c.Assert(dir.Actions().ActionSpecs, gc.HasLen, 0) +} + +func (s *CharmDirSuite) TestReadCharmDirWithActions(c *gc.C) { + path := charmDirPath(c, "dummy-actions") + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Actions().ActionSpecs, gc.HasLen, 1) +} + +func (s *CharmDirSuite) TestReadCharmDirWithJujuActions(c *gc.C) { + path := charmDirPath(c, "juju-charm") + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Actions().ActionSpecs, gc.HasLen, 1) +} + +func (s *CharmDirSuite) TestReadCharmDirManifest(c *gc.C) { + path := charmDirPath(c, "dummy") + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + + c.Assert(dir.Manifest().Bases, gc.DeepEquals, []charm.Base{{ + Name: "ubuntu", + Channel: charm.Channel{ + Track: "18.04", + Risk: "stable", + }, + }, { + Name: "ubuntu", + Channel: charm.Channel{ + Track: "20.04", + Risk: "stable", + }, + }}) +} + +func (s *CharmDirSuite) TestReadCharmDirWithoutManifest(c *gc.C) { + path := charmDirPath(c, "mysql") + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Manifest(), gc.IsNil) +} + +func (s *CharmDirSuite) TestArchiveTo(c *gc.C) { + baseDir := c.MkDir() + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + s.assertArchiveTo(c, baseDir, charmDir) +} + +func (s *CharmDirSuite) TestArchiveToWithIgnoredFiles(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + // Add a directory/files that should be ignored + nestedGitDir := filepath.Join(dir.Path, ".git/nested") + err = os.MkdirAll(nestedGitDir, 0700) + c.Assert(err, jc.ErrorIsNil) + + f, err := os.Create(filepath.Join(nestedGitDir, "foo")) + c.Assert(err, jc.ErrorIsNil) + _ = f.Close() + + // Ensure that we cannot spoof the version or revision files + err = ioutil.WriteFile(filepath.Join(dir.Path, "version"), []byte("spoofed version"), 0644) + c.Assert(err, jc.ErrorIsNil) + err = ioutil.WriteFile(filepath.Join(dir.Path, "revision"), []byte("42"), 0644) + c.Assert(err, jc.ErrorIsNil) + + var b bytes.Buffer + err = dir.ArchiveTo(&b) + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadCharmArchiveBytes(b.Bytes()) + c.Assert(err, jc.ErrorIsNil) + + manifest, err := archive.ArchiveMembers() + c.Assert(err, jc.ErrorIsNil) + c.Assert(manifest, jc.DeepEquals, set.NewStrings(dummyArchiveMembers...)) + + c.Assert(archive.Version(), gc.Not(gc.Equals), "spoofed version") + c.Assert(archive.Revision(), gc.Not(gc.Equals), 42) +} + +func (s *CharmDirSuite) TestArchiveToWithJujuignore(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + + jujuignore := ` +# Ignore directory named "bar" anywhere in the charm +bar/ + +# Retain "tox" but ignore everything inside it EXCEPT "keep" +tox/** +!tox/keep +` + // Add .jujuignore + err := ioutil.WriteFile(filepath.Join(charmDir, ".jujuignore"), []byte(jujuignore), 0644) + c.Assert(err, jc.ErrorIsNil) + + // Add directory/files that should be ignored based on jujuignore rules + nestedDir := filepath.Join(charmDir, "foo/bar/baz") + err = os.MkdirAll(nestedDir, 0700) + c.Assert(err, jc.ErrorIsNil) + + toxDir := filepath.Join(charmDir, "tox") + err = os.MkdirAll(filepath.Join(toxDir, "data"), 0700) + c.Assert(err, jc.ErrorIsNil) + + f, err := os.Create(filepath.Join(toxDir, "keep")) + c.Assert(err, jc.ErrorIsNil) + _ = f.Close() + + f, err = os.Create(filepath.Join(toxDir, "ignore")) + c.Assert(err, jc.ErrorIsNil) + _ = f.Close() + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + var b bytes.Buffer + err = dir.ArchiveTo(&b) + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadCharmArchiveBytes(b.Bytes()) + c.Assert(err, jc.ErrorIsNil) + + // Based on the .jujuignore rules, we should retain "foo/bar" and + // "tox/keep" but nothing else + retained := []string{"foo", "tox", "tox/keep"} + expContents := set.NewStrings(append(retained, dummyArchiveMembers...)...) + + manifest, err := archive.ArchiveMembers() + c.Log(manifest.Difference(expContents)) + c.Log(expContents.Difference(manifest)) + c.Assert(err, jc.ErrorIsNil) + c.Assert(manifest, jc.DeepEquals, expContents) +} + +func (s *CharmSuite) TestArchiveToWithVersionString(c *gc.C) { + baseDir := c.MkDir() + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + testing.PatchExecutableAsEchoArgs(c, s, "git") + + // create an empty .execName file inside tempDir + vcsPath := filepath.Join(charmDir, ".git") + _, err := os.Create(vcsPath) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + path := filepath.Join(baseDir, "archive.charm") + file, err := os.Create(path) + c.Assert(err, jc.ErrorIsNil) + + err = dir.ArchiveTo(file) + _ = file.Close() + c.Assert(err, jc.ErrorIsNil) + + args := []string{"describe", "--dirty", "--always"} + testing.AssertEchoArgs(c, "git", args...) + + zipr, err := zip.OpenReader(path) + c.Assert(err, jc.ErrorIsNil) + defer zipr.Close() + + var verf *zip.File + for _, f := range zipr.File { + if f.Name == "version" { + verf = f + } + } + + c.Assert(verf, gc.NotNil) + reader, err := verf.Open() + c.Assert(err, jc.ErrorIsNil) + data, err := ioutil.ReadAll(reader) + _ = reader.Close() + c.Assert(err, jc.ErrorIsNil) + + obtainedData := string(data) + obtainedData = strings.TrimSuffix(obtainedData, "\n") + + expectedArg := "git" + for _, arg := range args { + expectedArg = fmt.Sprintf("%s %s", expectedArg, utils.ShQuote(arg)) + } + c.Assert(obtainedData, gc.Equals, expectedArg) +} + +func (s *CharmSuite) TestMaybeGenerateVersionStringHasAVersionFile(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + versionFile := filepath.Join(charmDir, "version") + f, err := os.Create(versionFile) + c.Assert(err, jc.ErrorIsNil) + defer f.Close() + + expectedVersionNumber := "123456789abc" + _, err = f.WriteString(expectedVersionNumber) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + version, vcsType, err := dir.MaybeGenerateVersionString() + c.Assert(version, gc.Equals, expectedVersionNumber) + + VersionFileType := "versionFile" + c.Assert(vcsType, gc.Equals, VersionFileType) + + c.Assert(err, jc.ErrorIsNil) +} + +func (s *CharmSuite) TestReadCharmDirNoLogging(c *gc.C) { + var tw loggo.TestWriter + err := loggo.RegisterWriter("versionstring-test", &tw) + c.Assert(err, jc.ErrorIsNil) + defer loggo.RemoveWriter("versionstring-test") + + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Version(), gc.Equals, "") + + noLogging := jc.SimpleMessages{} + c.Assert(tw.Log(), jc.LogMatches, noLogging) +} + +func (s *CharmSuite) TestArchiveToWithVersionStringError(c *gc.C) { + baseDir := c.MkDir() + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + + // create an empty .execName file inside tempDir + vcsPath := filepath.Join(charmDir, ".git") + _, err := os.Create(vcsPath) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + path := filepath.Join(baseDir, "archive.charm") + file, err := os.Create(path) + c.Assert(err, jc.ErrorIsNil) + + testing.PatchExecutableThrowError(c, s, "git", 128) + var tw loggo.TestWriter + err = loggo.RegisterWriter("versionstring-test", &tw) + c.Assert(err, jc.ErrorIsNil) + defer loggo.RemoveWriter("versionstring-test") + + msg := fmt.Sprintf("%q version string generation failed : exit status 128\nThis means that the charm version won't show in juju status. Charm path %q", "git", dir.Path) + + _, _, err = dir.MaybeGenerateVersionString() + c.Assert(err, gc.ErrorMatches, msg) + + err = dir.ArchiveTo(file) + _ = file.Close() + c.Assert(err, jc.ErrorIsNil) + + c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{ + Level: loggo.WARNING, Message: msg, + }}) + + zipr, err := zip.OpenReader(path) + c.Assert(err, jc.ErrorIsNil) + defer zipr.Close() + + for _, f := range zipr.File { + if f.Name == "version" { + c.Fatal("unexpected version in charm archive") + } + } +} + +func (s *CharmDirSuite) TestArchiveToWithSymlinkedRootDir(c *gc.C) { + path := cloneDir(c, charmDirPath(c, "dummy")) + baseDir := filepath.Dir(path) + err := os.Symlink(filepath.Join("dummy"), filepath.Join(baseDir, "newdummy")) + c.Assert(err, jc.ErrorIsNil) + charmDir := filepath.Join(baseDir, "newdummy") + + s.assertArchiveTo(c, baseDir, charmDir) +} + +func (s *CharmDirSuite) assertArchiveTo(c *gc.C, baseDir, charmDir string) { + haveSymlinks := true + if err := os.Symlink("../target", filepath.Join(charmDir, "hooks/symlink")); err != nil { + haveSymlinks = false + } + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + path := filepath.Join(baseDir, "archive.charm") + + file, err := os.Create(path) + c.Assert(err, jc.ErrorIsNil) + + err = dir.ArchiveTo(file) + c.Assert(err, jc.ErrorIsNil) + + err = file.Close() + c.Assert(err, jc.ErrorIsNil) + + zipr, err := zip.OpenReader(path) + c.Assert(err, jc.ErrorIsNil) + defer zipr.Close() + + var metaf, instf, emptyf, revf, symf *zip.File + for _, f := range zipr.File { + c.Logf("Archived file: %s", f.Name) + switch f.Name { + case "revision": + revf = f + case "metadata.yaml": + metaf = f + case "hooks/install": + instf = f + case "hooks/symlink": + symf = f + case "empty/": + emptyf = f + case "build/ignored": + c.Errorf("archive includes build/*: %s", f.Name) + case ".ignored", ".dir/ignored": + c.Errorf("archive includes .* entries: %s", f.Name) + } + } + + c.Assert(revf, gc.NotNil) + reader, err := revf.Open() + c.Assert(err, jc.ErrorIsNil) + data, err := ioutil.ReadAll(reader) + reader.Close() + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(data), gc.Equals, "1") + + c.Assert(metaf, gc.NotNil) + reader, err = metaf.Open() + c.Assert(err, jc.ErrorIsNil) + meta, err := charm.ReadMeta(reader) + reader.Close() + c.Assert(err, jc.ErrorIsNil) + c.Assert(meta.Name, gc.Equals, "dummy") + + c.Assert(instf, gc.NotNil) + // Despite it being 0751, we pack and unpack it as 0755. + c.Assert(instf.Mode()&0777, gc.Equals, os.FileMode(0755)) + + if haveSymlinks { + c.Assert(symf, gc.NotNil) + c.Assert(symf.Mode()&0777, gc.Equals, os.FileMode(0777)) + reader, err = symf.Open() + c.Assert(err, jc.ErrorIsNil) + data, err = ioutil.ReadAll(reader) + reader.Close() + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(data), gc.Equals, "../target") + } else { + c.Assert(symf, gc.IsNil) + } + + c.Assert(emptyf, gc.NotNil) + c.Assert(emptyf.Mode()&os.ModeType, gc.Equals, os.ModeDir) + // Despite it being 0750, we pack and unpack it as 0755. + c.Assert(emptyf.Mode()&0777, gc.Equals, os.FileMode(0755)) +} + +// Bug #864164: Must complain if charm hooks aren't executable +func (s *CharmDirSuite) TestArchiveToWithNonExecutableHooks(c *gc.C) { + hooks := []string{"install", "start", "config-changed", "upgrade-charm", "stop"} + for _, relName := range []string{"foo", "bar", "self"} { + for _, kind := range []string{"joined", "changed", "departed", "broken"} { + hooks = append(hooks, relName+"-relation-"+kind) + } + } + + dir := readCharmDir(c, "all-hooks") + path := filepath.Join(c.MkDir(), "archive.charm") + file, err := os.Create(path) + c.Assert(err, jc.ErrorIsNil) + err = dir.ArchiveTo(file) + file.Close() + c.Assert(err, jc.ErrorIsNil) + + tlog := c.GetTestLog() + for _, hook := range hooks { + fullpath := filepath.Join(dir.Path, "hooks", hook) + exp := fmt.Sprintf(`^(.|\n)*WARNING juju.charm making "%s" executable in charm(.|\n)*$`, fullpath) + c.Assert(tlog, gc.Matches, exp, gc.Commentf("hook %q was not made executable", fullpath)) + } + + // Expand it and check the hooks' permissions + // (But do not use ExpandTo(), just use the raw zip) + f, err := os.Open(path) + c.Assert(err, jc.ErrorIsNil) + defer f.Close() + fi, err := f.Stat() + c.Assert(err, jc.ErrorIsNil) + size := fi.Size() + zipr, err := zip.NewReader(f, size) + c.Assert(err, jc.ErrorIsNil) + allhooks := dir.Meta().Hooks() + for _, zfile := range zipr.File { + cleanName := filepath.Clean(zfile.Name) + if strings.HasPrefix(cleanName, "hooks") { + hookName := filepath.Base(cleanName) + if _, ok := allhooks[hookName]; ok { + perms := zfile.Mode() + c.Assert(perms&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", hookName)) + } + } + } +} + +func (s *CharmDirSuite) TestArchiveToWithBadType(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + badFile := filepath.Join(charmDir, "hooks", "badfile") + + // Symlink targeting a path outside of the charm. + err := os.Symlink("../../target", badFile) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + err = dir.ArchiveTo(&bytes.Buffer{}) + c.Assert(err, gc.ErrorMatches, `symlink "hooks/badfile" links out of charm: "../../target"`) + + // Symlink targeting an absolute path. + os.Remove(badFile) + err = os.Symlink("/target", badFile) + c.Assert(err, jc.ErrorIsNil) + + dir, err = charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + err = dir.ArchiveTo(&bytes.Buffer{}) + c.Assert(err, gc.ErrorMatches, `symlink "hooks/badfile" is absolute: "/target"`) + + // Can't archive special files either. + os.Remove(badFile) + err = syscall.Mkfifo(badFile, 0644) + c.Assert(err, jc.ErrorIsNil) + + dir, err = charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + err = dir.ArchiveTo(&bytes.Buffer{}) + c.Assert(err, gc.ErrorMatches, `file is a named pipe: "hooks/badfile"`) +} + +func (s *CharmDirSuite) TestDirRevisionFile(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + revPath := filepath.Join(charmDir, "revision") + + // Missing revision file + err := os.Remove(revPath) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Revision(), gc.Equals, 0) + + // Missing revision file with obsolete old revision in metadata ignores + // the old revision field. + file, err := os.OpenFile(filepath.Join(charmDir, "metadata.yaml"), os.O_WRONLY|os.O_APPEND, 0) + c.Assert(err, jc.ErrorIsNil) + _, err = file.Write([]byte("\nrevision: 1234\n")) + c.Assert(err, jc.ErrorIsNil) + + dir, err = charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Revision(), gc.Equals, 0) + + // Revision file with bad content + err = ioutil.WriteFile(revPath, []byte("garbage"), 0666) + c.Assert(err, jc.ErrorIsNil) + + dir, err = charm.ReadCharmDir(charmDir) + c.Assert(err, gc.ErrorMatches, "invalid revision file") + c.Assert(dir, gc.IsNil) +} + +func (s *CharmDirSuite) TestDirSetRevision(c *gc.C) { + path := cloneDir(c, charmDirPath(c, "dummy")) + dir, err := charm.ReadCharmDir(path) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Revision(), gc.Equals, 1) + dir.SetRevision(42) + c.Assert(dir.Revision(), gc.Equals, 42) + + var b bytes.Buffer + err = dir.ArchiveTo(&b) + c.Assert(err, jc.ErrorIsNil) + + archive, err := charm.ReadCharmArchiveBytes(b.Bytes()) + c.Assert(err, jc.ErrorIsNil) + c.Assert(archive.Revision(), gc.Equals, 42) +} + +func (s *CharmDirSuite) TestDirSetDiskRevision(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + c.Assert(dir.Revision(), gc.Equals, 1) + dir.SetDiskRevision(42) + c.Assert(dir.Revision(), gc.Equals, 42) + + dir, err = charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + c.Assert(dir.Revision(), gc.Equals, 42) +} + +func (s *CharmSuite) TestMaybeGenerateVersionStringError(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + + testing.PatchExecutableThrowError(c, s, "git", 128) + vcsPath := filepath.Join(charmDir, ".git") + _, err := os.Create(vcsPath) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + version, vcsType, err := dir.MaybeGenerateVersionString() + msg := fmt.Sprintf("%q version string generation failed : exit status 128\nThis means that the charm version won't show in juju status. Charm path %q", "git", dir.Path) + c.Assert(err, gc.ErrorMatches, msg) + c.Assert(version, gc.Equals, "") + c.Assert(vcsType, gc.Equals, "git") +} + +func (s *CharmSuite) assertGenerateVersionString(c *gc.C, execName string, args []string) { + // Read the charmDir from the testing folder and clone all contents. + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + + testing.PatchExecutableAsEchoArgs(c, s, execName) + + // create an empty .execName file inside tempDir + vcsPath := filepath.Join(charmDir, "."+execName) + _, err := os.Create(vcsPath) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + version, vcsType, err := dir.MaybeGenerateVersionString() + c.Assert(err, jc.ErrorIsNil) + + version = strings.Trim(version, "\n") + version = strings.Replace(version, "'", "", -1) + expectedVersion := strings.Join(append([]string{execName}, args...), " ") + c.Assert(version, gc.Equals, expectedVersion) + c.Assert(vcsType, gc.Equals, execName) + + testing.AssertEchoArgs(c, execName, args...) +} + +// TestCreateMaybeGenerateVersionString verifies if the version string can be generated +// in case of git revision control directory +func (s *CharmSuite) TestGitMaybeGenerateVersionString(c *gc.C) { + s.assertGenerateVersionString(c, "git", []string{"describe", "--dirty", "--always"}) +} + +// TestBzrMaybeGenaretVersionString verifies if the version string can be generated +// in case of bazaar revision control directory. +func (s *CharmSuite) TestBazaarMaybeGenerateVersionString(c *gc.C) { + s.assertGenerateVersionString(c, "bzr", []string{"version-info"}) +} + +// TestHgMaybeGenerateVersionString verifies if the version string can be generated +// in case of Mecurial revision control directory. +func (s *CharmSuite) TestHgMaybeGenerateVersionString(c *gc.C) { + s.assertGenerateVersionString(c, "hg", []string{"id", "-n"}) +} + +// TestNoVCSMaybeGenerateVersionString verifies that version string not generated +// in case of not a revision control directory. +func (s *CharmSuite) TestNoVCSMaybeGenerateVersionString(c *gc.C) { + // Read the charmDir from the testing folder and clone the contents. + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + + dir, err := charm.ReadCharmDir(charmDir) + c.Assert(err, jc.ErrorIsNil) + + versionString, vcsType, err := dir.MaybeGenerateVersionString() + c.Assert(err, jc.ErrorIsNil) + c.Assert(versionString, gc.Equals, "") + c.Assert(vcsType, gc.Equals, "") +} + +// TestMaybeGenerateVersionStringUsesAbsolutePathGitVersion verifies that using a relative path still works. +func (s *CharmSuite) TestMaybeGenerateVersionStringUsesAbsolutePathGitVersion(c *gc.C) { + // Read the relativePath from the testing folder. + relativePath := charmDirPath(c, "dummy") + dir, err := charm.ReadCharmDir(relativePath) + c.Assert(err, jc.ErrorIsNil) + + versionString, vcsType, err := dir.MaybeGenerateVersionString() + c.Assert(err, jc.ErrorIsNil) + c.Assert(versionString, gc.Not(gc.Equals), "") + c.Assert(vcsType, gc.Equals, "git") +} + +// TestMaybeGenerateVersionStringLogsAbsolutePath verifies that the absolute path gets logged. +func (s *CharmSuite) TestMaybeGenerateVersionStringLogsAbsolutePath(c *gc.C) { + var tw loggo.TestWriter + ctx := loggo.DefaultContext() + err := ctx.AddWriter("versionstring-test", &tw) + c.Assert(err, jc.ErrorIsNil) + + logger := ctx.GetLogger("juju.testing") + lvl, _ := loggo.ParseLevel("TRACE") + logger.SetLogLevel(lvl) + defer func() { _, _ = loggo.RemoveWriter("versionstring-test") }() + defer loggo.ResetLogging() + + testing.PatchExecutableThrowError(c, s, "git", 128) + + // Read the relativePath from the testing folder. + relativePath := charmDirPath(c, "dummy") + absPath, err := filepath.Abs(relativePath) + c.Assert(err, jc.ErrorIsNil) + + dir, err := charm.ReadCharmDir(relativePath, charm.WithLogger(internallogger.WrapLoggo(logger))) + c.Assert(err, jc.ErrorIsNil) + + expectedMsg := fmt.Sprintf("charm is not versioned, charm path %q", absPath) + + versionString, vcsType, err := dir.MaybeGenerateVersionString() + c.Assert(len(tw.Log()), gc.Equals, 1) + c.Assert(tw.Log()[0].Message, gc.Matches, expectedMsg) + c.Assert(err, jc.ErrorIsNil) + c.Assert(versionString, gc.Matches, "") + c.Assert(vcsType, gc.Equals, "") +} + +// We expect it to be successful because we set the timeout to be high and the executable "git" returns error code 0 +func (s *CharmSuite) TestCheckGitIsUsed(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + testing.PatchExecutableAsEchoArgs(c, s, "git") + cmdWaitTime := 100 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), cmdWaitTime) + isUsing := charm.UsesGit(ctx, charmDir, cancel, loggertesting.WrapCheckLog(c)) + c.Assert(isUsing, gc.Equals, true) +} + +// We create the executable "git" and still expect it to "fail" because we set the timeout to be 0 +func (s *CharmSuite) TestCheckGitTimeout(c *gc.C) { + charmDir := cloneDir(c, charmDirPath(c, "dummy")) + testing.PatchExecutableAsEchoArgs(c, s, "git") + cmdWaitTime := 0 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), cmdWaitTime) + isUsing := charm.UsesGit(ctx, charmDir, cancel, loggertesting.WrapCheckLog(c)) + c.Assert(isUsing, gc.Equals, false) +} diff --git a/internal/charm/config.go b/internal/charm/config.go new file mode 100644 index 00000000000..5eb05d92c34 --- /dev/null +++ b/internal/charm/config.go @@ -0,0 +1,265 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "io" + "io/ioutil" + "net/url" + "strconv" + + "github.com/juju/errors" + "github.com/juju/schema" + "gopkg.in/yaml.v2" +) + +// Settings is a group of charm config option names and values. A Settings +// S is considered valid by the Config C if every key in S is an option in +// C, and every value either has the correct type or is nil. +type Settings map[string]interface{} + +// Option represents a single charm config option. +type Option struct { + Type string `yaml:"type"` + Description string `yaml:"description,omitempty"` + Default interface{} `yaml:"default,omitempty"` +} + +// error replaces any supplied non-nil error with a new error describing a +// validation failure for the supplied value. +func (option Option) error(err *error, name string, value interface{}) { + if *err != nil { + *err = fmt.Errorf("option %q expected %s, got %#v", name, option.Type, value) + } +} + +const secretScheme = "secret" + +type secretC struct{} + +// Coerce implements schema.Checker.Coerce for secretC. +func (c secretC) Coerce(v interface{}, path []string) (interface{}, error) { + s, err := schema.String().Coerce(v, path) + if err != nil { + return nil, err + } + str := s.(string) + if str == "" { + return "", nil + } + u, err := url.Parse(str) + if err != nil { + return nil, errors.Trace(err) + } + if u.Scheme == "" { + return nil, errors.NotValidf("secret URI scheme missing") + } + if u.Scheme != secretScheme { + return nil, errors.NotValidf("secret URI scheme %q", u.Scheme) + } + return str, nil +} + +// validate returns an appropriately-typed value for the supplied value, or +// returns an error if it cannot be converted to the correct type. Nil values +// are always considered valid. +func (option Option) validate(name string, value interface{}) (_ interface{}, err error) { + if value == nil { + return nil, nil + } + if checker := optionTypeCheckers[option.Type]; checker != nil { + defer option.error(&err, name, value) + if value, err = checker.Coerce(value, nil); err != nil { + return nil, err + } + return value, nil + } + return nil, fmt.Errorf("option %q has unknown type %q", name, option.Type) +} + +var optionTypeCheckers = map[string]schema.Checker{ + "string": schema.String(), + "int": schema.Int(), + "float": schema.Float(), + "boolean": schema.Bool(), + "secret": secretC{}, +} + +func (option Option) parse(name, str string) (val interface{}, err error) { + switch option.Type { + case "string", "secret": + return str, nil + case "int": + val, err = strconv.ParseInt(str, 10, 64) + case "float": + val, err = strconv.ParseFloat(str, 64) + case "boolean": + val, err = strconv.ParseBool(str) + default: + return nil, fmt.Errorf("option %q has unknown type %q", name, option.Type) + } + + defer option.error(&err, name, str) + return +} + +// Config represents the supported configuration options for a charm, +// as declared in its config.yaml file. +type Config struct { + Options map[string]Option +} + +// NewConfig returns a new Config without any options. +func NewConfig() *Config { + return &Config{map[string]Option{}} +} + +// ReadConfig reads a Config in YAML format. +func ReadConfig(r io.Reader) (*Config, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + var config *Config + if err := yaml.Unmarshal(data, &config); err != nil { + return nil, err + } + if config == nil { + return nil, fmt.Errorf("invalid config: empty configuration") + } + if config.Options == nil { + // We are allowed an empty configuration if the options + // field is explicitly specified, but there is no easy way + // to tell if it was specified or not without unmarshaling + // into interface{} and explicitly checking the field. + var configInterface interface{} + if err := yaml.Unmarshal(data, &configInterface); err != nil { + return nil, err + } + m, _ := configInterface.(map[interface{}]interface{}) + if _, ok := m["options"]; !ok { + return nil, fmt.Errorf("invalid config: empty configuration") + } + } + for name, option := range config.Options { + switch option.Type { + case "string", "secret", "int", "float", "boolean": + case "": + // Missing type is valid in python. + option.Type = "string" + default: + return nil, fmt.Errorf("invalid config: option %q has unknown type %q", name, option.Type) + } + def := option.Default + if def == "" && (option.Type == "string" || option.Type == "secret") { + // Skip normal validation for compatibility with pyjuju. + } else if option.Default, err = option.validate(name, def); err != nil { + option.error(&err, name, def) + return nil, fmt.Errorf("invalid config default: %v", err) + } + config.Options[name] = option + } + return config, nil +} + +// option returns the named option from the config, or an error if none +// such exists. +func (c *Config) option(name string) (Option, error) { + if option, ok := c.Options[name]; ok { + return option, nil + } + return Option{}, fmt.Errorf("unknown option %q", name) +} + +// DefaultSettings returns settings containing the default value of every +// option in the config. Default values may be nil. +func (c *Config) DefaultSettings() Settings { + out := make(Settings) + for name, option := range c.Options { + out[name] = option.Default + } + return out +} + +// ValidateSettings returns a copy of the supplied settings with a consistent type +// for each value. It returns an error if the settings contain unknown keys +// or invalid values. +func (c *Config) ValidateSettings(settings Settings) (Settings, error) { + out := make(Settings) + for name, value := range settings { + if option, err := c.option(name); err != nil { + return nil, err + } else if value, err = option.validate(name, value); err != nil { + return nil, err + } + out[name] = value + } + return out, nil +} + +// FilterSettings returns the subset of the supplied settings that are valid. +func (c *Config) FilterSettings(settings Settings) Settings { + out := make(Settings) + for name, value := range settings { + if option, err := c.option(name); err == nil { + if value, err := option.validate(name, value); err == nil { + out[name] = value + } + } + } + return out +} + +// ParseSettingsStrings returns settings derived from the supplied map. Every +// value in the map must be parseable to the correct type for the option +// identified by its key. Empty values are interpreted as nil. +func (c *Config) ParseSettingsStrings(values map[string]string) (Settings, error) { + out := make(Settings) + for name, str := range values { + option, err := c.option(name) + if err != nil { + return nil, err + } + value, err := option.parse(name, str) + if err != nil { + return nil, err + } + out[name] = value + } + return out, nil +} + +// ParseSettingsYAML returns settings derived from the supplied YAML data. The +// YAML must unmarshal to a map of strings to settings data; the supplied key +// must be present in the map, and must point to a map in which every value +// must have, or be a string parseable to, the correct type for the associated +// config option. Empty strings and nil values are both interpreted as nil. +func (c *Config) ParseSettingsYAML(yamlData []byte, key string) (Settings, error) { + var allSettings map[string]Settings + if err := yaml.Unmarshal(yamlData, &allSettings); err != nil { + return nil, fmt.Errorf("cannot parse settings data: %v", err) + } + settings, ok := allSettings[key] + if !ok { + return nil, fmt.Errorf("no settings found for %q", key) + } + out := make(Settings) + for name, value := range settings { + option, err := c.option(name) + if err != nil { + return nil, err + } + // Accept string values for compatibility with python. + if str, ok := value.(string); ok { + if value, err = option.parse(name, str); err != nil { + return nil, err + } + } else if value, err = option.validate(name, value); err != nil { + return nil, err + } + out[name] = value + } + return out, nil +} diff --git a/internal/charm/config_test.go b/internal/charm/config_test.go new file mode 100644 index 00000000000..2648df23e04 --- /dev/null +++ b/internal/charm/config_test.go @@ -0,0 +1,500 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "bytes" + "fmt" + "strings" + + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/internal/charm" +) + +type ConfigSuite struct { + config *charm.Config +} + +var _ = gc.Suite(&ConfigSuite{}) + +func (s *ConfigSuite) SetUpSuite(c *gc.C) { + // Just use a single shared config for the whole suite. There's no use case + // for mutating a config, we assume that nobody will do so here. + var err error + s.config, err = charm.ReadConfig(bytes.NewBuffer([]byte(` +options: + title: + default: My Title + description: A descriptive title used for the application. + type: string + subtitle: + default: "" + description: An optional subtitle used for the application. + outlook: + description: No default outlook. + # type defaults to string in python + username: + default: admin001 + description: The name of the initial account (given admin permissions). + type: string + skill-level: + description: A number indicating skill. + type: int + agility-ratio: + description: A number from 0 to 1 indicating agility. + type: float + reticulate-splines: + description: Whether to reticulate splines on launch, or not. + type: boolean + secret-foo: + description: A secret value. + type: secret +`))) + c.Assert(err, gc.IsNil) +} + +func (s *ConfigSuite) TestReadSample(c *gc.C) { + c.Assert(s.config.Options, jc.DeepEquals, map[string]charm.Option{ + "title": { + Default: "My Title", + Description: "A descriptive title used for the application.", + Type: "string", + }, + "subtitle": { + Default: "", + Description: "An optional subtitle used for the application.", + Type: "string", + }, + "username": { + Default: "admin001", + Description: "The name of the initial account (given admin permissions).", + Type: "string", + }, + "outlook": { + Description: "No default outlook.", + Type: "string", + }, + "skill-level": { + Description: "A number indicating skill.", + Type: "int", + }, + "agility-ratio": { + Description: "A number from 0 to 1 indicating agility.", + Type: "float", + }, + "reticulate-splines": { + Description: "Whether to reticulate splines on launch, or not.", + Type: "boolean", + }, + "secret-foo": { + Description: "A secret value.", + Type: "secret", + }, + }) +} + +func (s *ConfigSuite) TestDefaultSettings(c *gc.C) { + c.Assert(s.config.DefaultSettings(), jc.DeepEquals, charm.Settings{ + "title": "My Title", + "subtitle": "", + "username": "admin001", + "secret-foo": nil, + "outlook": nil, + "skill-level": nil, + "agility-ratio": nil, + "reticulate-splines": nil, + }) +} + +func (s *ConfigSuite) TestFilterSettings(c *gc.C) { + settings := s.config.FilterSettings(charm.Settings{ + "title": "something valid", + "username": nil, + "unknown": "whatever", + "outlook": "", + "skill-level": 5.5, + "agility-ratio": true, + "reticulate-splines": "hullo", + }) + c.Assert(settings, jc.DeepEquals, charm.Settings{ + "title": "something valid", + "username": nil, + "outlook": "", + }) +} + +func (s *ConfigSuite) TestValidateSettings(c *gc.C) { + for i, test := range []struct { + info string + input charm.Settings + expect charm.Settings + err string + }{ + { + info: "nil settings are valid", + expect: charm.Settings{}, + }, { + info: "empty settings are valid", + input: charm.Settings{}, + }, { + info: "unknown keys are not valid", + input: charm.Settings{"foo": nil}, + err: `unknown option "foo"`, + }, { + info: "nil is valid for every value type", + input: charm.Settings{ + "outlook": nil, + "skill-level": nil, + "agility-ratio": nil, + "reticulate-splines": nil, + }, + }, { + info: "correctly-typed values are valid", + input: charm.Settings{ + "outlook": "stormy", + "skill-level": int64(123), + "agility-ratio": 0.5, + "reticulate-splines": true, + }, + }, { + info: "empty string-typed values stay empty", + input: charm.Settings{"outlook": ""}, + expect: charm.Settings{"outlook": ""}, + }, { + info: "almost-correctly-typed values are valid", + input: charm.Settings{ + "skill-level": 123, + "agility-ratio": float32(0.5), + }, + expect: charm.Settings{ + "skill-level": int64(123), + "agility-ratio": 0.5, + }, + }, { + info: "bad string", + input: charm.Settings{"outlook": false}, + err: `option "outlook" expected string, got false`, + }, { + info: "bad int", + input: charm.Settings{"skill-level": 123.4}, + err: `option "skill-level" expected int, got 123.4`, + }, { + info: "bad float", + input: charm.Settings{"agility-ratio": "cheese"}, + err: `option "agility-ratio" expected float, got "cheese"`, + }, { + info: "bad boolean", + input: charm.Settings{"reticulate-splines": 101}, + err: `option "reticulate-splines" expected boolean, got 101`, + }, { + info: "invalid secret", + input: charm.Settings{"secret-foo": "cheese"}, + err: `option "secret-foo" expected secret, got "cheese"`, + }, { + info: "valid secret", + input: charm.Settings{"secret-foo": "secret:cj4v5vm78ohs79o84r4g"}, + expect: charm.Settings{"secret-foo": "secret:cj4v5vm78ohs79o84r4g"}, + }, + } { + c.Logf("test %d: %s", i, test.info) + result, err := s.config.ValidateSettings(test.input) + if test.err != "" { + c.Check(err, gc.ErrorMatches, test.err) + } else { + c.Check(err, gc.IsNil) + if test.expect == nil { + c.Check(result, jc.DeepEquals, test.input) + } else { + c.Check(result, jc.DeepEquals, test.expect) + } + } + } +} + +var settingsWithNils = charm.Settings{ + "outlook": nil, + "skill-level": nil, + "agility-ratio": nil, + "reticulate-splines": nil, +} + +var settingsWithValues = charm.Settings{ + "outlook": "whatever", + "skill-level": int64(123), + "agility-ratio": 2.22, + "reticulate-splines": true, +} + +func (s *ConfigSuite) TestParseSettingsYAML(c *gc.C) { + for i, test := range []struct { + info string + yaml string + key string + expect charm.Settings + err string + }{{ + info: "bad structure", + yaml: "`", + err: `cannot parse settings data: .*`, + }, { + info: "bad key", + yaml: "{}", + key: "blah", + err: `no settings found for "blah"`, + }, { + info: "bad settings key", + yaml: "blah:\n ping: pong", + key: "blah", + err: `unknown option "ping"`, + }, { + info: "bad type for string", + yaml: "blah:\n outlook: 123", + key: "blah", + err: `option "outlook" expected string, got 123`, + }, { + info: "bad type for int", + yaml: "blah:\n skill-level: 12.345", + key: "blah", + err: `option "skill-level" expected int, got 12.345`, + }, { + info: "bad type for float", + yaml: "blah:\n agility-ratio: blob", + key: "blah", + err: `option "agility-ratio" expected float, got "blob"`, + }, { + info: "bad type for boolean", + yaml: "blah:\n reticulate-splines: 123", + key: "blah", + err: `option "reticulate-splines" expected boolean, got 123`, + }, { + info: "bad string for int", + yaml: "blah:\n skill-level: cheese", + key: "blah", + err: `option "skill-level" expected int, got "cheese"`, + }, { + info: "bad string for float", + yaml: "blah:\n agility-ratio: blob", + key: "blah", + err: `option "agility-ratio" expected float, got "blob"`, + }, { + info: "bad string for boolean", + yaml: "blah:\n reticulate-splines: cannonball", + key: "blah", + err: `option "reticulate-splines" expected boolean, got "cannonball"`, + }, { + info: "empty dict is valid", + yaml: "blah: {}", + key: "blah", + expect: charm.Settings{}, + }, { + info: "nil values are valid", + yaml: `blah: + outlook: null + skill-level: null + agility-ratio: null + reticulate-splines: null`, + key: "blah", + expect: settingsWithNils, + }, { + info: "empty strings for bool options are not accepted", + yaml: `blah: + outlook: "" + skill-level: 123 + agility-ratio: 12.0 + reticulate-splines: ""`, + key: "blah", + err: `option "reticulate-splines" expected boolean, got ""`, + }, { + info: "empty strings for int options are not accepted", + yaml: `blah: + outlook: "" + skill-level: "" + agility-ratio: 12.0 + reticulate-splines: false`, + key: "blah", + err: `option "skill-level" expected int, got ""`, + }, { + info: "empty strings for float options are not accepted", + yaml: `blah: + outlook: "" + skill-level: 123 + agility-ratio: "" + reticulate-splines: false`, + key: "blah", + err: `option "agility-ratio" expected float, got ""`, + }, { + info: "appropriate strings are valid", + yaml: `blah: + outlook: whatever + skill-level: "123" + agility-ratio: "2.22" + reticulate-splines: "true"`, + key: "blah", + expect: settingsWithValues, + }, { + info: "appropriate types are valid", + yaml: `blah: + outlook: whatever + skill-level: 123 + agility-ratio: 2.22 + reticulate-splines: y`, + key: "blah", + expect: settingsWithValues, + }} { + c.Logf("test %d: %s", i, test.info) + result, err := s.config.ParseSettingsYAML([]byte(test.yaml), test.key) + if test.err != "" { + c.Check(err, gc.ErrorMatches, test.err) + } else { + c.Check(err, gc.IsNil) + c.Check(result, jc.DeepEquals, test.expect) + } + } +} + +func (s *ConfigSuite) TestParseSettingsStrings(c *gc.C) { + for i, test := range []struct { + info string + input map[string]string + expect charm.Settings + err string + }{{ + info: "nil map is valid", + expect: charm.Settings{}, + }, { + info: "empty map is valid", + input: map[string]string{}, + expect: charm.Settings{}, + }, { + info: "empty strings for string options are valid", + input: map[string]string{"outlook": ""}, + expect: charm.Settings{"outlook": ""}, + }, { + info: "empty strings for non-string options are invalid", + input: map[string]string{"skill-level": ""}, + err: `option "skill-level" expected int, got ""`, + }, { + info: "strings are converted", + input: map[string]string{ + "outlook": "whatever", + "skill-level": "123", + "agility-ratio": "2.22", + "reticulate-splines": "true", + }, + expect: settingsWithValues, + }, { + info: "bad string for int", + input: map[string]string{"skill-level": "cheese"}, + err: `option "skill-level" expected int, got "cheese"`, + }, { + info: "bad string for float", + input: map[string]string{"agility-ratio": "blob"}, + err: `option "agility-ratio" expected float, got "blob"`, + }, { + info: "bad string for boolean", + input: map[string]string{"reticulate-splines": "cannonball"}, + err: `option "reticulate-splines" expected boolean, got "cannonball"`, + }} { + c.Logf("test %d: %s", i, test.info) + result, err := s.config.ParseSettingsStrings(test.input) + if test.err != "" { + c.Check(err, gc.ErrorMatches, test.err) + } else { + c.Check(err, gc.IsNil) + c.Check(result, jc.DeepEquals, test.expect) + } + } +} + +func (s *ConfigSuite) TestConfigError(c *gc.C) { + _, err := charm.ReadConfig(bytes.NewBuffer([]byte(`options: {t: {type: foo}}`))) + c.Assert(err, gc.ErrorMatches, `invalid config: option "t" has unknown type "foo"`) +} + +func (s *ConfigSuite) TestConfigWithNoOptions(c *gc.C) { + _, err := charm.ReadConfig(strings.NewReader("other:\n")) + c.Assert(err, gc.ErrorMatches, "invalid config: empty configuration") + + _, err = charm.ReadConfig(strings.NewReader("\n")) + c.Assert(err, gc.ErrorMatches, "invalid config: empty configuration") + + _, err = charm.ReadConfig(strings.NewReader("null\n")) + c.Assert(err, gc.ErrorMatches, "invalid config: empty configuration") + + _, err = charm.ReadConfig(strings.NewReader("options:\n")) + c.Assert(err, gc.IsNil) +} + +func (s *ConfigSuite) TestDefaultType(c *gc.C) { + assertDefault := func(type_ string, value string, expected interface{}) { + config := fmt.Sprintf(`options: {x: {type: %s, default: %s}}`, type_, value) + result, err := charm.ReadConfig(bytes.NewBuffer([]byte(config))) + c.Assert(err, gc.IsNil) + c.Assert(result.Options["x"].Default, gc.Equals, expected) + } + + assertDefault("boolean", "true", true) + assertDefault("string", "golden grahams", "golden grahams") + assertDefault("string", `""`, "") + assertDefault("float", "2.211", 2.211) + assertDefault("int", "99", int64(99)) + + assertTypeError := func(type_, str, value string) { + config := fmt.Sprintf(`options: {t: {type: %s, default: %s}}`, type_, str) + _, err := charm.ReadConfig(bytes.NewBuffer([]byte(config))) + expected := fmt.Sprintf(`invalid config default: option "t" expected %s, got %s`, type_, value) + c.Assert(err, gc.ErrorMatches, expected) + } + + assertTypeError("boolean", "henry", `"henry"`) + assertTypeError("string", "2.5", "2.5") + assertTypeError("float", "123a", `"123a"`) + assertTypeError("int", "true", "true") +} + +// When an empty config is supplied an error should be returned +func (s *ConfigSuite) TestEmptyConfigReturnsError(c *gc.C) { + config := "" + result, err := charm.ReadConfig(bytes.NewBuffer([]byte(config))) + c.Assert(result, gc.IsNil) + c.Assert(err, gc.ErrorMatches, "invalid config: empty configuration") +} + +func (s *ConfigSuite) TestYAMLMarshal(c *gc.C) { + cfg, err := charm.ReadConfig(strings.NewReader(` +options: + minimal: + type: string + withdescription: + type: int + description: d + withdefault: + type: boolean + description: d + default: true +`)) + c.Assert(err, gc.IsNil) + c.Assert(cfg.Options, gc.HasLen, 3) + + newYAML, err := yaml.Marshal(cfg) + c.Assert(err, gc.IsNil) + + newCfg, err := charm.ReadConfig(bytes.NewReader(newYAML)) + c.Assert(err, gc.IsNil) + c.Assert(newCfg, jc.DeepEquals, cfg) +} + +func (s *ConfigSuite) TestErrorOnInvalidOptionTypes(c *gc.C) { + cfg := charm.Config{ + Options: map[string]charm.Option{"testOption": {Type: "invalid type"}}, + } + _, err := cfg.ParseSettingsYAML([]byte("testKey:\n testOption: 12.345"), "testKey") + c.Assert(err, gc.ErrorMatches, "option \"testOption\" has unknown type \"invalid type\"") + + _, err = cfg.ParseSettingsYAML([]byte("testKey:\n testOption: \"some string value\""), "testKey") + c.Assert(err, gc.ErrorMatches, "option \"testOption\" has unknown type \"invalid type\"") +} diff --git a/internal/charm/downloader/downloader.go b/internal/charm/downloader/downloader.go index 1afc614dacc..e1fe1baafcf 100644 --- a/internal/charm/downloader/downloader.go +++ b/internal/charm/downloader/downloader.go @@ -10,8 +10,8 @@ import ( "os" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/utils/v4" "github.com/juju/juju/core/arch" diff --git a/internal/charm/downloader/downloader_test.go b/internal/charm/downloader/downloader_test.go index ce28b420762..50dce9927d2 100644 --- a/internal/charm/downloader/downloader_test.go +++ b/internal/charm/downloader/downloader_test.go @@ -11,7 +11,7 @@ import ( "os" "path/filepath" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/internal/charm/downloader/mocks/charm_archive_mocks.go b/internal/charm/downloader/mocks/charm_archive_mocks.go index 21d9e3cc13d..8673bff54be 100644 --- a/internal/charm/downloader/mocks/charm_archive_mocks.go +++ b/internal/charm/downloader/mocks/charm_archive_mocks.go @@ -14,7 +14,7 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charm0 "github.com/juju/juju/core/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/internal/charm/downloader/mocks/charm_mocks.go b/internal/charm/downloader/mocks/charm_mocks.go index 24623f81f64..7f565ef3451 100644 --- a/internal/charm/downloader/mocks/charm_mocks.go +++ b/internal/charm/downloader/mocks/charm_mocks.go @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) diff --git a/internal/charm/export_test.go b/internal/charm/export_test.go new file mode 100644 index 00000000000..36766c613e9 --- /dev/null +++ b/internal/charm/export_test.go @@ -0,0 +1,22 @@ +// Copyright 2011-2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +// Export meaningful bits for tests only. + +var ( + IfaceExpander = ifaceExpander + + ParsePayloadClass = parsePayloadClass + ResourceSchema = resourceSchema + ExtraBindingsSchema = extraBindingsSchema + ValidateMetaExtraBindings = validateMetaExtraBindings + ParseResourceMeta = parseResourceMeta + + UsesGit = usesGit +) + +func MissingSeriesError() error { + return errMissingSeries +} diff --git a/internal/charm/extra_bindings.go b/internal/charm/extra_bindings.go new file mode 100644 index 00000000000..661252c3667 --- /dev/null +++ b/internal/charm/extra_bindings.go @@ -0,0 +1,76 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "strings" + + "github.com/juju/collections/set" + "github.com/juju/schema" +) + +// ExtraBinding represents an extra bindable endpoint that is not a relation. +type ExtraBinding struct { + Name string `bson:"name" json:"Name"` +} + +// When specified, the "extra-bindings" section in the metadata.yaml +// should have the following format: +// +// extra-bindings: +// +// "": +// ... +// +// Endpoint names are strings and must not match existing relation names from +// the Provides, Requires, or Peers metadata sections. The values beside each +// endpoint name must be left out (i.e. "foo": is invalid). +var extraBindingsSchema = schema.Map(schema.NonEmptyString("binding name"), schema.Nil("")) + +func parseMetaExtraBindings(data interface{}) (map[string]ExtraBinding, error) { + if data == nil { + return nil, nil + } + + bindingsMap := data.(map[interface{}]interface{}) + result := make(map[string]ExtraBinding) + for name := range bindingsMap { + stringName := name.(string) + result[stringName] = ExtraBinding{Name: stringName} + } + + return result, nil +} + +func validateMetaExtraBindings(meta Meta) error { + extraBindings := meta.ExtraBindings + if extraBindings == nil { + return nil + } else if len(extraBindings) == 0 { + return fmt.Errorf("extra bindings cannot be empty when specified") + } + + usedExtraNames := set.NewStrings() + for name, binding := range extraBindings { + if binding.Name == "" || name == "" { + return fmt.Errorf("missing binding name") + } + if binding.Name != name { + return fmt.Errorf("mismatched extra binding name: got %q, expected %q", binding.Name, name) + } + usedExtraNames.Add(name) + } + + usedRelationNames := set.NewStrings() + for relationName := range meta.CombinedRelations() { + usedRelationNames.Add(relationName) + } + notAllowedNames := usedExtraNames.Intersection(usedRelationNames) + if !notAllowedNames.IsEmpty() { + notAllowedList := strings.Join(notAllowedNames.SortedValues(), ", ") + return fmt.Errorf("relation names (%s) cannot be used in extra bindings", notAllowedList) + } + return nil +} diff --git a/internal/charm/extra_bindings_test.go b/internal/charm/extra_bindings_test.go new file mode 100644 index 00000000000..a245ff2d825 --- /dev/null +++ b/internal/charm/extra_bindings_test.go @@ -0,0 +1,69 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +var _ = gc.Suite(&extraBindingsSuite{}) + +type extraBindingsSuite struct { + riakMeta charm.Meta +} + +func (s *extraBindingsSuite) SetUpTest(c *gc.C) { + riakMeta, err := charm.ReadMeta(repoMeta(c, "riak")) + c.Assert(err, jc.ErrorIsNil) + s.riakMeta = *riakMeta +} + +func (s *extraBindingsSuite) TestSchemaOkay(c *gc.C) { + raw := map[interface{}]interface{}{ + "foo": nil, + "bar": nil, + } + v, err := charm.ExtraBindingsSchema.Coerce(raw, nil) + c.Assert(err, jc.ErrorIsNil) + + c.Check(v, jc.DeepEquals, map[interface{}]interface{}{ + "foo": nil, + "bar": nil, + }) +} + +func (s *extraBindingsSuite) TestValidateWithEmptyNonNilMap(c *gc.C) { + s.riakMeta.ExtraBindings = map[string]charm.ExtraBinding{} + err := charm.ValidateMetaExtraBindings(s.riakMeta) + c.Assert(err, gc.ErrorMatches, "extra bindings cannot be empty when specified") +} + +func (s *extraBindingsSuite) TestValidateWithEmptyName(c *gc.C) { + s.riakMeta.ExtraBindings = map[string]charm.ExtraBinding{ + "": {Name: ""}, + } + err := charm.ValidateMetaExtraBindings(s.riakMeta) + c.Assert(err, gc.ErrorMatches, "missing binding name") +} + +func (s *extraBindingsSuite) TestValidateWithMismatchedName(c *gc.C) { + s.riakMeta.ExtraBindings = map[string]charm.ExtraBinding{ + "bar": {Name: "foo"}, + } + err := charm.ValidateMetaExtraBindings(s.riakMeta) + c.Assert(err, gc.ErrorMatches, `mismatched extra binding name: got "foo", expected "bar"`) +} + +func (s *extraBindingsSuite) TestValidateWithRelationNamesMatchingExtraBindings(c *gc.C) { + s.riakMeta.ExtraBindings = map[string]charm.ExtraBinding{ + "admin": {Name: "admin"}, + "ring": {Name: "ring"}, + "foo": {Name: "foo"}, + } + err := charm.ValidateMetaExtraBindings(s.riakMeta) + c.Assert(err, gc.ErrorMatches, `relation names \(admin, ring\) cannot be used in extra bindings`) +} diff --git a/internal/charm/hooks/hooks.go b/internal/charm/hooks/hooks.go new file mode 100644 index 00000000000..f064a3ba0f9 --- /dev/null +++ b/internal/charm/hooks/hooks.go @@ -0,0 +1,173 @@ +// Copyright 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +// Package hooks provides types and constants that define the hooks known to Juju. +package hooks + +// Kind enumerates the different kinds of hooks that exist. +type Kind string + +const ( + // None of these hooks are ever associated with a relation; each of them + // represents a change to the state of the unit as a whole. The values + // themselves are all valid hook names. + + Install Kind = "install" + Start Kind = "start" + ConfigChanged Kind = "config-changed" + UpgradeCharm Kind = "upgrade-charm" + Stop Kind = "stop" + Remove Kind = "remove" + Action Kind = "action" + LeaderElected Kind = "leader-elected" + LeaderDeposed Kind = "leader-deposed" + LeaderSettingsChanged Kind = "leader-settings-changed" + UpdateStatus Kind = "update-status" + PreSeriesUpgrade Kind = "pre-series-upgrade" + PostSeriesUpgrade Kind = "post-series-upgrade" + SecretChanged Kind = "secret-changed" + SecretExpired Kind = "secret-expired" + SecretRemove Kind = "secret-remove" + SecretRotate Kind = "secret-rotate" + + // These hooks require an associated relation, and the name of the relation + // unit whose change triggered the hook. The hook file names that these + // kinds represent will be prefixed by the relation name; for example, + // "db-relation-joined". + + RelationCreated Kind = "relation-created" + RelationJoined Kind = "relation-joined" + RelationChanged Kind = "relation-changed" + RelationDeparted Kind = "relation-departed" + + // This hook requires an associated relation. The represented hook file name + // will be prefixed by the relation name, just like the other Relation* Kind + // values. + + RelationBroken Kind = "relation-broken" + + // These hooks require an associated storage. The hook file names that these + // kinds represent will be prefixed by the storage name; for example, + // "shared-fs-storage-attached". + + StorageAttached Kind = "storage-attached" + StorageDetaching Kind = "storage-detaching" + + // These hooks require an associated workload/container, and the name of the workload/container + // whose change triggered the hook. The hook file names that these + // kinds represent will be prefixed by the workload/container name; for example, + // "mycontainer-pebble-ready". + + PebbleChangeUpdated Kind = "pebble-change-updated" + PebbleCustomNotice Kind = "pebble-custom-notice" + PebbleReady Kind = "pebble-ready" +) + +var unitHooks = []Kind{ + Install, + Start, + ConfigChanged, + UpgradeCharm, + Stop, + Remove, + LeaderElected, + LeaderDeposed, + LeaderSettingsChanged, + UpdateStatus, + PreSeriesUpgrade, + PostSeriesUpgrade, +} + +// UnitHooks returns all known unit hook kinds. +func UnitHooks() []Kind { + hooks := make([]Kind, len(unitHooks)) + copy(hooks, unitHooks) + return hooks +} + +var relationHooks = []Kind{ + RelationCreated, + RelationJoined, + RelationChanged, + RelationDeparted, + RelationBroken, +} + +// RelationHooks returns all known relation hook kinds. +func RelationHooks() []Kind { + hooks := make([]Kind, len(relationHooks)) + copy(hooks, relationHooks) + return hooks +} + +var storageHooks = []Kind{ + StorageAttached, + StorageDetaching, +} + +// StorageHooks returns all known storage hook kinds. +func StorageHooks() []Kind { + hooks := make([]Kind, len(storageHooks)) + copy(hooks, storageHooks) + return hooks +} + +var workloadHooks = []Kind{ + PebbleChangeUpdated, + PebbleCustomNotice, + PebbleReady, +} + +// WorkloadHooks returns all known container hook kinds. +func WorkloadHooks() []Kind { + hooks := make([]Kind, len(workloadHooks)) + copy(hooks, workloadHooks) + return hooks +} + +// IsRelation returns whether the Kind represents a relation hook. +func (kind Kind) IsRelation() bool { + switch kind { + case RelationCreated, RelationJoined, RelationChanged, RelationDeparted, RelationBroken: + return true + } + return false +} + +// IsStorage returns whether the Kind represents a storage hook. +func (kind Kind) IsStorage() bool { + switch kind { + case StorageAttached, StorageDetaching: + return true + } + return false +} + +// IsWorkload returns whether the Kind represents a workload hook. +func (kind Kind) IsWorkload() bool { + switch kind { + case PebbleChangeUpdated, PebbleCustomNotice, PebbleReady: + return true + } + return false +} + +var secretHooks = []Kind{ + SecretChanged, SecretExpired, SecretRemove, SecretRotate, +} + +// SecretHooks returns all secret hook kinds. +func SecretHooks() []Kind { + hooks := make([]Kind, len(secretHooks)) + copy(hooks, secretHooks) + return hooks +} + +// IsSecret returns whether the Kind represents a secret hook. +func (kind Kind) IsSecret() bool { + switch kind { + case SecretChanged, SecretExpired, SecretRemove, SecretRotate: + return true + } + return false +} diff --git a/internal/charm/hooks/hooks_test.go b/internal/charm/hooks/hooks_test.go new file mode 100644 index 00000000000..38583746e14 --- /dev/null +++ b/internal/charm/hooks/hooks_test.go @@ -0,0 +1,49 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package hooks + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" +) + +var _ = gc.Suite(&HooksSuite{}) + +type HooksSuite struct{} + +func (s *HooksSuite) TestIsRelation(c *gc.C) { + for _, h := range relationHooks { + c.Assert(h.IsRelation(), jc.IsTrue) + } + for _, h := range unitHooks { + c.Assert(h.IsRelation(), jc.IsFalse) + } +} + +func (s *HooksSuite) TestIsStorage(c *gc.C) { + for _, h := range storageHooks { + c.Assert(h.IsStorage(), jc.IsTrue) + } + for _, h := range unitHooks { + c.Assert(h.IsStorage(), jc.IsFalse) + } +} + +func (s *HooksSuite) TestIsWorkload(c *gc.C) { + for _, h := range workloadHooks { + c.Assert(h.IsWorkload(), jc.IsTrue) + } + for _, h := range unitHooks { + c.Assert(h.IsWorkload(), jc.IsFalse) + } +} + +func (s *HooksSuite) TestIsSecret(c *gc.C) { + for _, h := range secretHooks { + c.Assert(h.IsSecret(), jc.IsTrue) + } + for _, h := range unitHooks { + c.Assert(h.IsSecret(), jc.IsFalse) + } +} diff --git a/internal/charm/hooks/package_test.go b/internal/charm/hooks/package_test.go new file mode 100644 index 00000000000..6d1a0895a2d --- /dev/null +++ b/internal/charm/hooks/package_test.go @@ -0,0 +1,14 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package hooks_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func Test(t *testing.T) { + gc.TestingT(t) +} diff --git a/internal/charm/internal/test-charm-repo/bundle/bad/README.md b/internal/charm/internal/test-charm-repo/bundle/bad/README.md new file mode 100644 index 00000000000..8654e582bbb --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/bad/README.md @@ -0,0 +1 @@ +A dummy bundle diff --git a/internal/charm/internal/test-charm-repo/bundle/bad/bundle.yaml b/internal/charm/internal/test-charm-repo/bundle/bad/bundle.yaml new file mode 100644 index 00000000000..312957be27a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/bad/bundle.yaml @@ -0,0 +1,11 @@ +# This bundle has a bad relation, which will cause it to fail +# its verification. +applications: + wordpress: + charm: wordpress + num_units: 1 + mysql: + charm: mysql + num_units: 1 +relations: + - ["foo:db", "mysql:server"] diff --git a/internal/charm/internal/test-charm-repo/bundle/openstack/README.md b/internal/charm/internal/test-charm-repo/bundle/openstack/README.md new file mode 100644 index 00000000000..db68e113e32 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/openstack/README.md @@ -0,0 +1,46 @@ +OpenStack Bundle for Juju +========================= + +Overview +-------- + +This bundle deploys a reference OpenStack architecture including all core projects: + + - OpenStack Compute + - OpenStack Networking (using Open vSwitch plugin) + - OpenStack Block Storage (backed with Ceph storage) + - OpenStack Image + - OpenStack Object Storage + - OpenStack Identity + - OpenStack Dashboard + - OpenStack Telemetry + - OpenStack Orchestration + +The charm configuration is an opinioned set for deploying OpenStack for testing on Cloud environments which support nested KVM. Instance types also need to have ephemeral storage (these block devices are used for Ceph and Swift storage). + +The Ubuntu Server Team use this bundle for testing OpenStack-on-OpenStack. + +Usage +----- + +Once deployed, the cloud can be accessed either using the OpenStack command line tools or using the OpenStack Dashboard: + + http:///horizon + +The charms configure the 'admin' user with a password of 'openstack' by default. + +The OpenStack cloud deployed is completely clean; the charms don't attempt to configure networking or upload images. Read the OpenStack User Guide on how to configure your cloud for use: + + http://docs.openstack.org/user-guide/content/ + +Niggles +------- + +The neutron-gateway service requires a service unit with two network interfaces to provide full functionality; this part of OpenStack provides L3 routing between tenant networks and the rest of the world. Its possible todo this when testing on OpenStack by adding a second network interface to the neutron-gateway service: + + nova interface-attach --net-id + juju set neutron-gateway ext-port=eth1 + +Note that you will need to be running this bundle on an OpenStack cloud that supports MAC address learning of some description; this includes using OpenStack Havana with the Neutron Open vSwitch plugin. + +For actual OpenStack deployments, this service would reside of a physical server with network ports attached to both the internal network (for communication with nova-compute service units) and the external network (for inbound/outbound network access to/from instances within the cloud). diff --git a/internal/charm/internal/test-charm-repo/bundle/openstack/bundle.yaml b/internal/charm/internal/test-charm-repo/bundle/openstack/bundle.yaml new file mode 100644 index 00000000000..e7783f00ad9 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/openstack/bundle.yaml @@ -0,0 +1,202 @@ +series: jammy +applications: + mysql: + charm: mysql + constraints: mem=1G + options: + dataset-size: 50% + rabbitmq-server: + charm: rabbitmq-server + constraints: mem=1G + ceph: + charm: ceph + num_units: 3 + constraints: mem=1G + options: + monitor-count: 3 + fsid: 6547bd3e-1397-11e2-82e5-53567c8d32dc + monitor-secret: AQCXrnZQwI7KGBAAiPofmKEXKxu5bUzoYLVkbQ== + osd-devices: /dev/vdb + osd-reformat: "yes" + ephemeral-unmount: /mnt + keystone: + charm: keystone + constraints: mem=1G + options: + admin-password: openstack + admin-token: ubuntutesting + openstack-dashboard: + charm: openstack-dashboard + constraints: mem=1G + nova-compute: + charm: nova-compute + num_units: 3 + constraints: mem=4G + options: + config-flags: "auto_assign_floating_ip=False" + enable-live-migration: False + virt-type: kvm + nova-cloud-controller: + charm: nova-cloud-controller + constraints: mem=1G + options: + network-manager: Neutron + quantum-security-groups: "yes" + neutron-gateway: + charm: quantum-gateway + constraints: mem=1G + cinder: + charm: cinder + options: + block-device: "None" + constraints": mem=1G + glance: + charm: glance + constraints: mem=1G + swift-proxy: + charm: swift-proxy + constraints: mem=1G + options: + zone-assignment: manual + replicas: 3 + use-https: 'no' + swift-hash: fdfef9d4-8b06-11e2-8ac0-531c923c8fae + swift-storage-z1: + charm: swift-storage + constraints: mem=1G + options: + zone: 1 + block-device: vdb + overwrite: "true" + swift-storage-z2: + charm: swift-storage + constraints: mem=1G + options: + zone: 2 + block-device: vdb + overwrite: "true" + swift-storage-z3: + charm: swift-storage + constraints: mem=1G + options: + zone: 3 + block-device: vdb + overwrite: "true" + ceilometer: + charm: ceilometer + constraints: mem=1G + ceilometer-agent: + charm: ceilometer-agent + mongodb: + charm: mongodb + constraints: mem=1G + heat: + charm: heat + constraints: mem=1G + ntp: + charm: ntp +relations: + - - keystone:shared-db + - mysql:shared-db + - - nova-cloud-controller:shared-db + - mysql:shared-db + - - nova-cloud-controller:amqp + - rabbitmq-server:amqp + - - nova-cloud-controller:image-service + - glance:image-service + - - nova-cloud-controller:identity-service + - keystone:identity-service + - - nova-compute:cloud-compute + - nova-cloud-controller:cloud-compute + - - nova-compute:shared-db + - mysql:shared-db + - - nova-compute:amqp + - rabbitmq-server:amqp + - - nova-compute:image-service + - glance:image-service + - - nova-compute:ceph + - ceph:client + - - glance:shared-db + - mysql:shared-db + - - glance:identity-service + - keystone:identity-service + - - glance:ceph + - ceph:client + - - glance:image-service + - cinder:image-service + - - cinder:shared-db + - mysql:shared-db + - - cinder:amqp + - rabbitmq-server:amqp + - - cinder:cinder-volume-service + - nova-cloud-controller:cinder-volume-service + - - cinder:identity-service + - keystone:identity-service + - - cinder:ceph + - ceph:client + - - neutron-gateway:shared-db + - mysql:shared-db + - - neutron-gateway:amqp + - rabbitmq-server:amqp + - - neutron-gateway:quantum-network-service + - nova-cloud-controller:quantum-network-service + - - openstack-dashboard:identity-service + - keystone:identity-service + - - swift-proxy:identity-service + - keystone:identity-service + - - swift-proxy:swift-storage + - swift-storage-z1:swift-storage + - - swift-proxy:swift-storage + - swift-storage-z2:swift-storage + - - swift-proxy:swift-storage + - swift-storage-z3:swift-storage + - - ceilometer:identity-service + - keystone:identity-service + - - ceilometer:amqp + - rabbitmq-server:amqp + - - ceilometer:shared-db + - mongodb:database + - - ceilometer-agent:nova-ceilometer + - nova-compute:nova-ceilometer + - - ceilometer-agent:ceilometer-service + - ceilometer:ceilometer-service + - - heat:identity-service + - keystone:identity-service + - - heat:shared-db + - mysql:shared-db + - - heat:amqp + - rabbitmq-server:amqp + - - ntp:juju-info + - nova-compute:juju-info + - - ntp:juju-info + - nova-cloud-controller:juju-info + - - ntp:juju-info + - neutron-gateway:juju-info + - - ntp:juju-info + - ceph:juju-info + - - ntp:juju-info + - cinder:juju-info + - - ntp:juju-info + - keystone:juju-info + - - ntp:juju-info + - glance:juju-info + - - ntp:juju-info + - swift-proxy:juju-info + - - ntp:juju-info + - swift-storage-z1:juju-info + - - ntp:juju-info + - swift-storage-z2:juju-info + - - ntp:juju-info + - swift-storage-z3:juju-info + - - ntp:juju-info + - ceilometer:juju-info + - - ntp:juju-info + - mongodb:juju-info + - - ntp:juju-info + - rabbitmq-server:juju-info + - - ntp:juju-info + - mysql:juju-info + - - ntp:juju-info + - openstack-dashboard:juju-info + - - ntp:juju-info + - heat:juju-info diff --git a/internal/charm/internal/test-charm-repo/bundle/wordpress-multidoc/README.md b/internal/charm/internal/test-charm-repo/bundle/wordpress-multidoc/README.md new file mode 100644 index 00000000000..8654e582bbb --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/wordpress-multidoc/README.md @@ -0,0 +1 @@ +A dummy bundle diff --git a/internal/charm/internal/test-charm-repo/bundle/wordpress-multidoc/bundle.yaml b/internal/charm/internal/test-charm-repo/bundle/wordpress-multidoc/bundle.yaml new file mode 100644 index 00000000000..518c16464c6 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/wordpress-multidoc/bundle.yaml @@ -0,0 +1,23 @@ +applications: + wordpress: + charm: ch:wordpress + mysql: + charm: cs:mysql + num_units: 1 +relations: + - ["wordpress:db", "mysql:server"] +--- # overlay.yaml +applications: + wordpress: + offers: + offer1: + endpoints: + - "some-endpoint" +--- # overlay2.yaml +applications: + wordpress: + offers: + offer1: + acl: + admin: "admin" + foo: "consume" diff --git a/internal/charm/internal/test-charm-repo/bundle/wordpress-simple-multidoc/README.md b/internal/charm/internal/test-charm-repo/bundle/wordpress-simple-multidoc/README.md new file mode 100644 index 00000000000..8654e582bbb --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/wordpress-simple-multidoc/README.md @@ -0,0 +1 @@ +A dummy bundle diff --git a/internal/charm/internal/test-charm-repo/bundle/wordpress-simple-multidoc/bundle.yaml b/internal/charm/internal/test-charm-repo/bundle/wordpress-simple-multidoc/bundle.yaml new file mode 100644 index 00000000000..b98212e41de --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/wordpress-simple-multidoc/bundle.yaml @@ -0,0 +1,13 @@ +applications: + wordpress: + charm: ch:wordpress + mysql: + charm: ch:mysql + num_units: 1 +relations: + - ["wordpress:db", "mysql:server"] +--- +applications: + wordpress: + annotations: + foo: bar diff --git a/internal/charm/internal/test-charm-repo/bundle/wordpress-simple/README.md b/internal/charm/internal/test-charm-repo/bundle/wordpress-simple/README.md new file mode 100644 index 00000000000..8654e582bbb --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/wordpress-simple/README.md @@ -0,0 +1 @@ +A dummy bundle diff --git a/internal/charm/internal/test-charm-repo/bundle/wordpress-simple/bundle.yaml b/internal/charm/internal/test-charm-repo/bundle/wordpress-simple/bundle.yaml new file mode 100644 index 00000000000..88a4bfc138f --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/wordpress-simple/bundle.yaml @@ -0,0 +1,8 @@ +applications: + wordpress: + charm: ch:wordpress + mysql: + charm: ch:mysql + num_units: 1 +relations: + - ["wordpress:db", "mysql:server"] diff --git a/internal/charm/internal/test-charm-repo/bundle/wordpress-with-logging/README.md b/internal/charm/internal/test-charm-repo/bundle/wordpress-with-logging/README.md new file mode 100644 index 00000000000..8654e582bbb --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/wordpress-with-logging/README.md @@ -0,0 +1 @@ +A dummy bundle diff --git a/internal/charm/internal/test-charm-repo/bundle/wordpress-with-logging/bundle.yaml b/internal/charm/internal/test-charm-repo/bundle/wordpress-with-logging/bundle.yaml new file mode 100644 index 00000000000..130ba13761c --- /dev/null +++ b/internal/charm/internal/test-charm-repo/bundle/wordpress-with-logging/bundle.yaml @@ -0,0 +1,20 @@ +applications: + wordpress: + charm: ch:wordpress + num_units: 1 + bindings: + db: db + url: public + db-client: db + admin-api: public + mysql: + charm: ch:mysql + num_units: 1 + bindings: + server: db + logging: + charm: logging +relations: + - ["wordpress:db", "mysql:server"] + - ["wordpress:juju-info", "logging:info"] + - ["mysql:juju-info", "logging:info"] diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-broken b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-broken new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-broken @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-changed b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-changed new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-changed @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-departed b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-departed new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-departed @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-joined b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-joined new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/bar-relation-joined @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/config-changed b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/config-changed new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/config-changed @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-broken b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-broken new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-broken @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-changed b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-changed new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-changed @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-departed b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-departed new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-departed @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-joined b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-joined new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/foo-relation-joined @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/install b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/install new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/install @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/otherdata b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/otherdata new file mode 100644 index 00000000000..7b57bd29ea8 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/otherdata @@ -0,0 +1 @@ +some text diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-broken b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-broken new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-broken @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-changed b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-changed new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-changed @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-departed b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-departed new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-departed @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-joined b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-joined new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/self-relation-joined @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/start b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/start new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/start @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/stop b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/stop new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/stop @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/subdir/stuff b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/subdir/stuff new file mode 100644 index 00000000000..717bbd9b337 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/subdir/stuff @@ -0,0 +1 @@ +non hook related stuff diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/upgrade-charm b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/upgrade-charm new file mode 100644 index 00000000000..e994810165a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/hooks/upgrade-charm @@ -0,0 +1,2 @@ +#!/bin/sh +echo $0 diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/manifest.yaml b/internal/charm/internal/test-charm-repo/quantal/all-hooks/manifest.yaml new file mode 100644 index 00000000000..d3852da9f78 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/manifest.yaml @@ -0,0 +1,5 @@ +bases: + - name: ubuntu + channel: 18.04/stable + - name: ubuntu + channel: "20.04" diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/all-hooks/metadata.yaml new file mode 100644 index 00000000000..75485d9f448 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/metadata.yaml @@ -0,0 +1,12 @@ +name: all-hooks +summary: "That's a dummy charm with hook scrips for all types of hooks." +description: "This is a longer description." +provides: + foo: + interface: phony +requires: + bar: + interface: fake +peers: + self: + interface: dummy diff --git a/internal/charm/internal/test-charm-repo/quantal/all-hooks/revision b/internal/charm/internal/test-charm-repo/quantal/all-hooks/revision new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/all-hooks/revision @@ -0,0 +1 @@ +1 diff --git a/internal/charm/internal/test-charm-repo/quantal/bad-bases/actions.yaml b/internal/charm/internal/test-charm-repo/quantal/bad-bases/actions.yaml new file mode 100644 index 00000000000..9ca45761b37 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad-bases/actions.yaml @@ -0,0 +1,7 @@ +snapshot: + description: Take a snapshot of the database. + params: + outfile: + description: The file to write out to. + type: string + default: foo.bz2 diff --git a/internal/charm/internal/test-charm-repo/quantal/bad-bases/config.yaml b/internal/charm/internal/test-charm-repo/quantal/bad-bases/config.yaml new file mode 100644 index 00000000000..a164f0a19a3 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad-bases/config.yaml @@ -0,0 +1,5 @@ +options: + title: {default: My Title, description: A descriptive title used for the application., type: string} + outlook: {description: No default outlook., type: string} + username: {default: admin001, description: The name of the initial account (given admin permissions)., type: string} + skill-level: {description: A number indicating skill., type: int} diff --git a/internal/charm/internal/test-charm-repo/quantal/bad-bases/hooks/install b/internal/charm/internal/test-charm-repo/quantal/bad-bases/hooks/install new file mode 100755 index 00000000000..1344650090d --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad-bases/hooks/install @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Done!" diff --git a/internal/charm/internal/test-charm-repo/quantal/bad-bases/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/bad-bases/metadata.yaml new file mode 100644 index 00000000000..e1b17cd31c8 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad-bases/metadata.yaml @@ -0,0 +1,20 @@ +name: a +summary: b +description: c +platforms: + - kubernetes +bases: + - name: ubuntu + channel: 18.04/stable +containers: + foo: + resource: test-os + mounts: + - storage: a + location: /b/ +resources: + test-os: + type: oci-image +storage: + a: + type: filesystem \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/bad-bases/revision b/internal/charm/internal/test-charm-repo/quantal/bad-bases/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad-bases/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/bad/actions.yaml b/internal/charm/internal/test-charm-repo/quantal/bad/actions.yaml new file mode 100644 index 00000000000..9ca45761b37 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad/actions.yaml @@ -0,0 +1,7 @@ +snapshot: + description: Take a snapshot of the database. + params: + outfile: + description: The file to write out to. + type: string + default: foo.bz2 diff --git a/internal/charm/internal/test-charm-repo/quantal/bad/config.yaml b/internal/charm/internal/test-charm-repo/quantal/bad/config.yaml new file mode 100644 index 00000000000..a164f0a19a3 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad/config.yaml @@ -0,0 +1,5 @@ +options: + title: {default: My Title, description: A descriptive title used for the application., type: string} + outlook: {description: No default outlook., type: string} + username: {default: admin001, description: The name of the initial account (given admin permissions)., type: string} + skill-level: {description: A number indicating skill., type: int} diff --git a/internal/charm/internal/test-charm-repo/quantal/bad/hooks/install b/internal/charm/internal/test-charm-repo/quantal/bad/hooks/install new file mode 100755 index 00000000000..1344650090d --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad/hooks/install @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Done!" diff --git a/internal/charm/internal/test-charm-repo/quantal/bad/revision b/internal/charm/internal/test-charm-repo/quantal/bad/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/bad/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/category/.dir/ignored b/internal/charm/internal/test-charm-repo/quantal/category/.dir/ignored new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/category/.ignored b/internal/charm/internal/test-charm-repo/quantal/category/.ignored new file mode 100644 index 00000000000..4287ca86179 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/category/.ignored @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/category/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/category/metadata.yaml new file mode 100644 index 00000000000..b70933eb7d7 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/category/metadata.yaml @@ -0,0 +1,6 @@ +name: categories +summary: "Sample charm with a category" +description: | + That's a boring charm that has a category. +categories: ["database"] +tags: ["openstack", "storage"] \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/.notignored b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/.notignored new file mode 100644 index 00000000000..4287ca86179 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/.notignored @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/actions.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/actions.yaml new file mode 100644 index 00000000000..9ca45761b37 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/actions.yaml @@ -0,0 +1,7 @@ +snapshot: + description: Take a snapshot of the database. + params: + outfile: + description: The file to write out to. + type: string + default: foo.bz2 diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/actions/snapshot b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/actions/snapshot new file mode 100644 index 00000000000..803b6b4e50a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/actions/snapshot @@ -0,0 +1,2 @@ +#!/bin/bash +echo "function $0" diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/build/ignored b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/build/ignored new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/config.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/config.yaml new file mode 100644 index 00000000000..a164f0a19a3 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/config.yaml @@ -0,0 +1,5 @@ +options: + title: {default: My Title, description: A descriptive title used for the application., type: string} + outlook: {description: No default outlook., type: string} + username: {default: admin001, description: The name of the initial account (given admin permissions)., type: string} + skill-level: {description: A number indicating skill., type: int} diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/empty/.gitkeep b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/empty/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/hooks/install b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/hooks/install new file mode 100755 index 00000000000..1344650090d --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/hooks/install @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Done!" diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/lxd-profile.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/lxd-profile.yaml new file mode 100644 index 00000000000..62070ccea83 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/lxd-profile.yaml @@ -0,0 +1,9 @@ +name: "test" +description: "sample lxdprofile for testing" +config: + security.nesting: "true" + security.privileged: "true" +devices: + tun: + path: /dev/net/tun + type: unix-char diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/manifest.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/manifest.yaml new file mode 100644 index 00000000000..d3852da9f78 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/manifest.yaml @@ -0,0 +1,5 @@ +bases: + - name: ubuntu + channel: 18.04/stable + - name: ubuntu + channel: "20.04" diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/metadata.yaml new file mode 100644 index 00000000000..42747427096 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/metadata.yaml @@ -0,0 +1,5 @@ +name: dummy +summary: "That's a dummy charm." +description: | + This is a longer description which + potentially contains multiple lines. diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/revision b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy-actions/src/hello.c b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/src/hello.c new file mode 100644 index 00000000000..9e37d332925 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy-actions/src/hello.c @@ -0,0 +1,7 @@ +#include + +main() +{ + printf ("Hello World!\n"); + return 0; +} diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/.notignored b/internal/charm/internal/test-charm-repo/quantal/dummy/.notignored new file mode 100644 index 00000000000..4287ca86179 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/.notignored @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/actions.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy/actions.yaml new file mode 100644 index 00000000000..9ca45761b37 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/actions.yaml @@ -0,0 +1,7 @@ +snapshot: + description: Take a snapshot of the database. + params: + outfile: + description: The file to write out to. + type: string + default: foo.bz2 diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/build/ignored b/internal/charm/internal/test-charm-repo/quantal/dummy/build/ignored new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/config.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy/config.yaml new file mode 100644 index 00000000000..a164f0a19a3 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/config.yaml @@ -0,0 +1,5 @@ +options: + title: {default: My Title, description: A descriptive title used for the application., type: string} + outlook: {description: No default outlook., type: string} + username: {default: admin001, description: The name of the initial account (given admin permissions)., type: string} + skill-level: {description: A number indicating skill., type: int} diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/empty/.gitkeep b/internal/charm/internal/test-charm-repo/quantal/dummy/empty/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/hooks/install b/internal/charm/internal/test-charm-repo/quantal/dummy/hooks/install new file mode 100755 index 00000000000..1344650090d --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/hooks/install @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Done!" diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/lxd-profile.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy/lxd-profile.yaml new file mode 100644 index 00000000000..62070ccea83 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/lxd-profile.yaml @@ -0,0 +1,9 @@ +name: "test" +description: "sample lxdprofile for testing" +config: + security.nesting: "true" + security.privileged: "true" +devices: + tun: + path: /dev/net/tun + type: unix-char diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/manifest.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy/manifest.yaml new file mode 100644 index 00000000000..d3852da9f78 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/manifest.yaml @@ -0,0 +1,5 @@ +bases: + - name: ubuntu + channel: 18.04/stable + - name: ubuntu + channel: "20.04" diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/dummy/metadata.yaml new file mode 100644 index 00000000000..42747427096 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/metadata.yaml @@ -0,0 +1,5 @@ +name: dummy +summary: "That's a dummy charm." +description: | + This is a longer description which + potentially contains multiple lines. diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/revision b/internal/charm/internal/test-charm-repo/quantal/dummy/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/dummy/src/hello.c b/internal/charm/internal/test-charm-repo/quantal/dummy/src/hello.c new file mode 100644 index 00000000000..9e37d332925 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/dummy/src/hello.c @@ -0,0 +1,7 @@ +#include + +main() +{ + printf ("Hello World!\n"); + return 0; +} diff --git a/internal/charm/internal/test-charm-repo/quantal/format-containers/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/format-containers/metadata.yaml new file mode 100644 index 00000000000..98769df9fe3 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format-containers/metadata.yaml @@ -0,0 +1,8 @@ +name: containers +summary: "Has containers" +containers: + containers: + snappass: + resource: snappass-image +description: | + Format V1 should be detected. diff --git a/internal/charm/internal/test-charm-repo/quantal/format-containersmanifest/manifest.yaml b/internal/charm/internal/test-charm-repo/quantal/format-containersmanifest/manifest.yaml new file mode 100644 index 00000000000..d3852da9f78 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format-containersmanifest/manifest.yaml @@ -0,0 +1,5 @@ +bases: + - name: ubuntu + channel: 18.04/stable + - name: ubuntu + channel: "20.04" diff --git a/internal/charm/internal/test-charm-repo/quantal/format-containersmanifest/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/format-containersmanifest/metadata.yaml new file mode 100644 index 00000000000..69c30047f4c --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format-containersmanifest/metadata.yaml @@ -0,0 +1,8 @@ +name: containers and manifest +summary: "Both manifest bases and containers present" +containers: + containers: + snappass: + resource: snappass-image +description: | + Format V1 should be detected. diff --git a/internal/charm/internal/test-charm-repo/quantal/format-series/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/format-series/metadata.yaml new file mode 100644 index 00000000000..3a426b1e125 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format-series/metadata.yaml @@ -0,0 +1,6 @@ +name: series +summary: "Has series present" +series: + - kubernetes +description: | + Format V1 should be detected. diff --git a/internal/charm/internal/test-charm-repo/quantal/format-seriesmanifest/manifest.yaml b/internal/charm/internal/test-charm-repo/quantal/format-seriesmanifest/manifest.yaml new file mode 100644 index 00000000000..d3852da9f78 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format-seriesmanifest/manifest.yaml @@ -0,0 +1,5 @@ +bases: + - name: ubuntu + channel: 18.04/stable + - name: ubuntu + channel: "20.04" diff --git a/internal/charm/internal/test-charm-repo/quantal/format-seriesmanifest/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/format-seriesmanifest/metadata.yaml new file mode 100644 index 00000000000..388008de830 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format-seriesmanifest/metadata.yaml @@ -0,0 +1,6 @@ +name: series-and-manifest +summary: "Both manifest bases and series present" +series: + - kubernetes +description: | + Format V1 should be detected. diff --git a/internal/charm/internal/test-charm-repo/quantal/format/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/format/metadata.yaml new file mode 100644 index 00000000000..11292bb4d16 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format/metadata.yaml @@ -0,0 +1,4 @@ +name: nothing +summary: "Has nothing" +description: | + Format V1 should be detected. diff --git a/internal/charm/internal/test-charm-repo/quantal/format2/.dir/ignored b/internal/charm/internal/test-charm-repo/quantal/format2/.dir/ignored new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/format2/.ignored b/internal/charm/internal/test-charm-repo/quantal/format2/.ignored new file mode 100644 index 00000000000..4287ca86179 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format2/.ignored @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/format2/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/format2/metadata.yaml new file mode 100644 index 00000000000..5e4f701d552 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/format2/metadata.yaml @@ -0,0 +1,6 @@ +name: format2 +format: 2 +summary: "Sample charm described in format 2" +description: | + That's a boring charm that is described in + terms of format 2. diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/.notignored b/internal/charm/internal/test-charm-repo/quantal/juju-charm/.notignored new file mode 100644 index 00000000000..4287ca86179 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/juju-charm/.notignored @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/actions.yaml b/internal/charm/internal/test-charm-repo/quantal/juju-charm/actions.yaml new file mode 100644 index 00000000000..3a41d41e4af --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/juju-charm/actions.yaml @@ -0,0 +1,7 @@ +juju-snapshot: + description: Take a snapshot of the database. + params: + outfile: + description: The file to write out to. + type: string + default: foo.bz2 diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/actions/juju-snapshot b/internal/charm/internal/test-charm-repo/quantal/juju-charm/actions/juju-snapshot new file mode 100644 index 00000000000..803b6b4e50a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/juju-charm/actions/juju-snapshot @@ -0,0 +1,2 @@ +#!/bin/bash +echo "function $0" diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/build/ignored b/internal/charm/internal/test-charm-repo/quantal/juju-charm/build/ignored new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/config.yaml b/internal/charm/internal/test-charm-repo/quantal/juju-charm/config.yaml new file mode 100644 index 00000000000..a164f0a19a3 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/juju-charm/config.yaml @@ -0,0 +1,5 @@ +options: + title: {default: My Title, description: A descriptive title used for the application., type: string} + outlook: {description: No default outlook., type: string} + username: {default: admin001, description: The name of the initial account (given admin permissions)., type: string} + skill-level: {description: A number indicating skill., type: int} diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/empty/.gitkeep b/internal/charm/internal/test-charm-repo/quantal/juju-charm/empty/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/hooks/install b/internal/charm/internal/test-charm-repo/quantal/juju-charm/hooks/install new file mode 100755 index 00000000000..1344650090d --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/juju-charm/hooks/install @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Done!" diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/lxd-profile.yaml b/internal/charm/internal/test-charm-repo/quantal/juju-charm/lxd-profile.yaml new file mode 100644 index 00000000000..62070ccea83 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/juju-charm/lxd-profile.yaml @@ -0,0 +1,9 @@ +name: "test" +description: "sample lxdprofile for testing" +config: + security.nesting: "true" + security.privileged: "true" +devices: + tun: + path: /dev/net/tun + type: unix-char diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/juju-charm/metadata.yaml new file mode 100644 index 00000000000..243aeda1245 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/juju-charm/metadata.yaml @@ -0,0 +1,8 @@ +name: juju-charm +summary: "That's a dummy charm." +description: | + This is a longer description which + potentially contains multiple lines. +provides: + dashboard: + interface: juju-dashboard diff --git a/internal/charm/internal/test-charm-repo/quantal/juju-charm/revision b/internal/charm/internal/test-charm-repo/quantal/juju-charm/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/juju-charm/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/logging/hooks/.gitkeep b/internal/charm/internal/test-charm-repo/quantal/logging/hooks/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/logging/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/logging/metadata.yaml new file mode 100644 index 00000000000..c9cd2ff0f95 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/logging/metadata.yaml @@ -0,0 +1,16 @@ +name: logging +summary: "Subordinate logging test charm" +description: | + This is a longer description which + potentially contains multiple lines. +subordinate: true +provides: + logging-client: + interface: logging +requires: + logging-directory: + interface: logging + scope: container + info: + interface: juju-info + scope: container diff --git a/internal/charm/internal/test-charm-repo/quantal/logging/revision b/internal/charm/internal/test-charm-repo/quantal/logging/revision new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/logging/revision @@ -0,0 +1 @@ +1 diff --git a/internal/charm/internal/test-charm-repo/quantal/monitoring/hooks/.gitkeep b/internal/charm/internal/test-charm-repo/quantal/monitoring/hooks/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/monitoring/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/monitoring/metadata.yaml new file mode 100644 index 00000000000..b392abab23d --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/monitoring/metadata.yaml @@ -0,0 +1,16 @@ +name: monitoring +summary: "Subordinate monitoring test charm" +description: | + This is a longer description which + potentially contains multiple lines. +subordinate: true +provides: + monitoring-client: + interface: monitoring +requires: + monitoring-port: + interface: monitoring + scope: container + info: + interface: juju-info + scope: container diff --git a/internal/charm/internal/test-charm-repo/quantal/mysql-alternative/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/mysql-alternative/metadata.yaml new file mode 100644 index 00000000000..754bacd5b6c --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/mysql-alternative/metadata.yaml @@ -0,0 +1,9 @@ +name: mysql-alternative +summary: "Database engine" +description: "A pretty popular database" +provides: + prod: + interface: mysql + dev: + interface: mysql + limit: 2 diff --git a/internal/charm/internal/test-charm-repo/quantal/mysql-alternative/revision b/internal/charm/internal/test-charm-repo/quantal/mysql-alternative/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/mysql-alternative/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/mysql/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/mysql/metadata.yaml new file mode 100644 index 00000000000..5f8c664c73c --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/mysql/metadata.yaml @@ -0,0 +1,5 @@ +name: mysql +summary: "Database engine" +description: "A pretty popular database" +provides: + server: mysql diff --git a/internal/charm/internal/test-charm-repo/quantal/mysql/revision b/internal/charm/internal/test-charm-repo/quantal/mysql/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/mysql/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/riak/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/riak/metadata.yaml new file mode 100644 index 00000000000..0b0eaa622b0 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/riak/metadata.yaml @@ -0,0 +1,11 @@ +name: riak +summary: "K/V storage engine" +description: "Scalable K/V Store in Erlang with Clocks :-)" +provides: + endpoint: + interface: http + admin: + interface: http +peers: + ring: + interface: riak diff --git a/internal/charm/internal/test-charm-repo/quantal/riak/revision b/internal/charm/internal/test-charm-repo/quantal/riak/revision new file mode 100644 index 00000000000..c7930257dfe --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/riak/revision @@ -0,0 +1 @@ +7 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/terms/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/terms/metadata.yaml new file mode 100644 index 00000000000..267a8f71d77 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/terms/metadata.yaml @@ -0,0 +1,5 @@ +name: terms +summary: "Sample charm with terms and conditions" +description: | + That's a boring charm that requires certain terms. +terms: ["term1/1", "term2", "owner/term3/1"] diff --git a/internal/charm/internal/test-charm-repo/quantal/terracotta/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/terracotta/metadata.yaml new file mode 100644 index 00000000000..b86afeb275e --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/terracotta/metadata.yaml @@ -0,0 +1,15 @@ +name: terracotta +summary: Distributed HA caching/storage platform for Java +maintainer: Robert Ayres +description: | + Distributed HA caching/storage platform for Java. + . + Terracotta provides out of the box clustering for a number of well known Java + frameworks, including EHCache, Hibernate and Quartz as well as clustering + for J2EE containers. +provides: + dso: + interface: terracotta + optional: true +peers: + server-array: terracotta-server diff --git a/internal/charm/internal/test-charm-repo/quantal/terracotta/revision b/internal/charm/internal/test-charm-repo/quantal/terracotta/revision new file mode 100644 index 00000000000..00750edc07d --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/terracotta/revision @@ -0,0 +1 @@ +3 diff --git a/internal/charm/internal/test-charm-repo/quantal/upgrade1/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/upgrade1/metadata.yaml new file mode 100644 index 00000000000..739c7195ca3 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/upgrade1/metadata.yaml @@ -0,0 +1,5 @@ +name: upgrade +summary: "Sample charm to test version changes" +description: | + Sample charm to test version changes. + This is the old charm. diff --git a/internal/charm/internal/test-charm-repo/quantal/upgrade1/revision b/internal/charm/internal/test-charm-repo/quantal/upgrade1/revision new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/upgrade1/revision @@ -0,0 +1 @@ +1 diff --git a/internal/charm/internal/test-charm-repo/quantal/upgrade2/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/upgrade2/metadata.yaml new file mode 100644 index 00000000000..e0a0fc409b3 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/upgrade2/metadata.yaml @@ -0,0 +1,5 @@ +name: upgrade +summary: "Sample charm to test version changes" +description: | + Sample charm to test version changes. + This is the new charm. diff --git a/internal/charm/internal/test-charm-repo/quantal/upgrade2/revision b/internal/charm/internal/test-charm-repo/quantal/upgrade2/revision new file mode 100644 index 00000000000..0cfbf08886f --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/upgrade2/revision @@ -0,0 +1 @@ +2 diff --git a/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/hooks/install b/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/hooks/install new file mode 100755 index 00000000000..dca28a3d25c --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/hooks/install @@ -0,0 +1,3 @@ +#!/bin/bash + +echo hello world \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/metadata.yaml new file mode 100644 index 00000000000..f086da3fa9a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/metadata.yaml @@ -0,0 +1,5 @@ +name: varnish-alternative +summary: "Database engine" +description: "Another popular database" +provides: + webcache: varnish diff --git a/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/revision b/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/varnish-alternative/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/varnish/manifest.yaml b/internal/charm/internal/test-charm-repo/quantal/varnish/manifest.yaml new file mode 100644 index 00000000000..d3852da9f78 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/varnish/manifest.yaml @@ -0,0 +1,5 @@ +bases: + - name: ubuntu + channel: 18.04/stable + - name: ubuntu + channel: "20.04" diff --git a/internal/charm/internal/test-charm-repo/quantal/varnish/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/varnish/metadata.yaml new file mode 100644 index 00000000000..28fd7fa37c6 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/varnish/metadata.yaml @@ -0,0 +1,5 @@ +name: varnish +summary: "Database engine" +description: "Another popular database" +provides: + webcache: varnish diff --git a/internal/charm/internal/test-charm-repo/quantal/varnish/revision b/internal/charm/internal/test-charm-repo/quantal/varnish/revision new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/varnish/revision @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/quantal/versioned/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/versioned/metadata.yaml new file mode 100644 index 00000000000..3d76baddf3a --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/versioned/metadata.yaml @@ -0,0 +1,5 @@ +name: versioned +summary: "Test charm with version file" +description: | + This is a longer description which + potentially contains multiple lines. diff --git a/internal/charm/internal/test-charm-repo/quantal/versioned/version b/internal/charm/internal/test-charm-repo/quantal/versioned/version new file mode 100644 index 00000000000..23a51789af5 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/versioned/version @@ -0,0 +1 @@ +929903d diff --git a/internal/charm/internal/test-charm-repo/quantal/wordpress/actions/.gitkeep b/internal/charm/internal/test-charm-repo/quantal/wordpress/actions/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/wordpress/config.yaml b/internal/charm/internal/test-charm-repo/quantal/wordpress/config.yaml new file mode 100644 index 00000000000..5453fb26b1d --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/wordpress/config.yaml @@ -0,0 +1,3 @@ +options: + blog-title: {default: My Title, description: A descriptive title used for the blog., type: string} + diff --git a/internal/charm/internal/test-charm-repo/quantal/wordpress/hooks/.gitkeep b/internal/charm/internal/test-charm-repo/quantal/wordpress/hooks/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/quantal/wordpress/manifest.yaml b/internal/charm/internal/test-charm-repo/quantal/wordpress/manifest.yaml new file mode 100644 index 00000000000..d3852da9f78 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/wordpress/manifest.yaml @@ -0,0 +1,5 @@ +bases: + - name: ubuntu + channel: 18.04/stable + - name: ubuntu + channel: "20.04" diff --git a/internal/charm/internal/test-charm-repo/quantal/wordpress/metadata.yaml b/internal/charm/internal/test-charm-repo/quantal/wordpress/metadata.yaml new file mode 100644 index 00000000000..a578421f9a8 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/wordpress/metadata.yaml @@ -0,0 +1,27 @@ +name: wordpress +summary: "Blog engine" +description: "A pretty popular blog engine" +provides: + url: + interface: http + limit: + optional: false + logging-dir: + interface: logging + scope: container + monitoring-port: + interface: monitoring + scope: container +requires: + db: + interface: mysql + limit: 1 + optional: false + cache: + interface: varnish + limit: 2 + optional: true +extra-bindings: + db-client: + admin-api: + foo-bar: diff --git a/internal/charm/internal/test-charm-repo/quantal/wordpress/revision b/internal/charm/internal/test-charm-repo/quantal/wordpress/revision new file mode 100644 index 00000000000..e440e5c8425 --- /dev/null +++ b/internal/charm/internal/test-charm-repo/quantal/wordpress/revision @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/internal/charm/internal/test-charm-repo/series/format2/build/ignored b/internal/charm/internal/test-charm-repo/series/format2/build/ignored new file mode 100644 index 00000000000..e69de29bb2d diff --git a/internal/charm/internal/test-charm-repo/series/format2/hooks/symlink b/internal/charm/internal/test-charm-repo/series/format2/hooks/symlink new file mode 120000 index 00000000000..78bc33729ba --- /dev/null +++ b/internal/charm/internal/test-charm-repo/series/format2/hooks/symlink @@ -0,0 +1 @@ +../target \ No newline at end of file diff --git a/internal/charm/jujuignore.go b/internal/charm/jujuignore.go new file mode 100644 index 00000000000..009bc2ba1be --- /dev/null +++ b/internal/charm/jujuignore.go @@ -0,0 +1,283 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "bufio" + "io" + "strings" + "unicode" + + "github.com/juju/errors" + + "gopkg.in/gobwas/glob.v0" +) + +var ( + ignorePatternReplacer = strings.NewReplacer( + "\\#", "#", + "\\!", "!", + "\\ ", " ", + ) +) + +type ruleResult uint8 + +const ( + // ruleResultKeep indicates that a file did not match an ignore + // rule and should be copied. + ruleResultKeep ruleResult = iota + + // ruleResultSkip indicates that a file matched an ignore rule and + // should not be copied. + ruleResultSkip + + // ruleResultKeep indicates that a file matched an inverted ignore rule + // and should be copied. + ruleResultForceKeep +) + +type ignoreRuleEvalFn func(path string, isDir bool) ruleResult + +// ignoreOnlyDirs constructs a ignoreRuleEvalFn that always returns +// ruleResultKeep for input paths that are not directories or the result of +// evaluating r for directory paths. +func ignoreOnlyDirs(r ignoreRuleEvalFn) ignoreRuleEvalFn { + return func(path string, isDir bool) ruleResult { + if !isDir { + return ruleResultKeep + } + + return r(path, isDir) + } +} + +// negateIgnoreRule constructs a ignoreRuleEvalFn that returns +// ruleResultForceKeep if r evaluates to ruleResultSkip. This function enables +// the construction of negateed ignore rules that force-include a file even +// though it was previously excluded by another rule. +func negateIgnoreRule(r ignoreRuleEvalFn) ignoreRuleEvalFn { + return func(path string, isDir bool) ruleResult { + if res := r(path, isDir); res == ruleResultSkip { + return ruleResultForceKeep + } + + return ruleResultKeep + } +} + +// ignoreGlobMatch constructs a ignoreRuleEvalFn that returns ruleResultSkip +// when the input matches any of the provided glob patterns. If an invalid glob +// pattern is provided then ignoreGlobMatch returns an error. +func ignoreGlobMatch(pattern string) (ignoreRuleEvalFn, error) { + var ( + err error + expandedPatterns = genIgnorePatternPermutations(pattern) + globPats = make([]glob.Glob, len(expandedPatterns)) + ) + + for i, pat := range expandedPatterns { + globPats[i], err = glob.Compile(pat, '/') + if err != nil { + return nil, err + } + } + + return func(path string, isDir bool) ruleResult { + for _, globPat := range globPats { + if globPat.Match(path) { + return ruleResultSkip + } + } + + return ruleResultKeep + }, nil +} + +type ignoreRuleset []ignoreRuleEvalFn + +// newIgnoreRuleset reads the contents of a .jujuignore file from r and returns +// back an ignoreRuleset that can be used to match files against the set of +// exclusion rules. +// +// .jujuignore files use the same syntax as .gitignore files. For more details +// see: https://git-scm.com/docs/gitignore#_pattern_format +func newIgnoreRuleset(r io.Reader) (ignoreRuleset, error) { + var ( + lineNo int + rs ignoreRuleset + s = bufio.NewScanner(r) + ) + + for s.Scan() { + lineNo++ + + // Cleanup leading whitespace; ignore empty and comment lines + rule := strings.TrimLeftFunc(s.Text(), unicode.IsSpace) + if len(rule) == 0 || rule[0] == '#' { + continue + } + + r, err := compileIgnoreRule(rule) + if err != nil { + return nil, errors.Annotatef(err, "[line %d]", lineNo) + } + + rs = append(rs, r) + } + + if err := s.Err(); err != nil { + return nil, err + } + + return rs, nil +} + +// Match returns true if path matches any of the ignore rules in the set. +func (rs ignoreRuleset) Match(path string, isDir bool) bool { + // To properly support start-of-pathname patterns all paths must + // begin with a / + if len(path) > 0 && path[0] != '/' { + path = "/" + path + } + + var keep = true + for _, r := range rs { + switch r(path, isDir) { + case ruleResultKeep: + // Keep file unless already excluded + if !keep { + continue + } + + keep = true + case ruleResultSkip: + keep = false + case ruleResultForceKeep: + // Keep file even if already excluded (inverted rule) + keep = true + } + } + + return !keep +} + +// compileIgnoreRule returns an ignoreRuleEvalFn for the provided rule. +func compileIgnoreRule(rule string) (ignoreRuleEvalFn, error) { + var ( + negateRule bool + applyToDirOnly bool + ) + + // If the rule begins with a '!' then the pattern is negated; any + // matching file excluded by a previous pattern will become included + // again. + if strings.HasPrefix(rule, "!") { + rule = strings.TrimPrefix(rule, "!") + negateRule = true + } + + rule = unescapeIgnorePattern(rule) + + // If the rule ends in a '/' then the slash is stripped off but the + // rule will only apply to directories. + if strings.HasSuffix(rule, "/") { + rule = strings.TrimSuffix(rule, "/") + applyToDirOnly = true + } + + // A leading "**" followed by a slash means match in all directories. + // "**/foo" is equivalent to "foo/bar" so we can actually trim it. + rule = strings.TrimPrefix(rule, "**/") + + // A leading slash matches the beginning of the pathname. For example, + // "/*.go" matches "foo.go" but not "bar/foo.go". In all other cases + // the pattern applies at any location (substring pattern) and we need + // to prefix it with "**/" (** behaves like * but also matches path + // separators) + if !strings.HasPrefix(rule, "/") { + rule = "**/" + rule + } + + fn, err := ignoreGlobMatch(rule) + if err != nil { + return nil, err + } + + if applyToDirOnly { + fn = ignoreOnlyDirs(fn) + } + + if negateRule { + fn = negateIgnoreRule(fn) + } + + return fn, nil +} + +// unescapeIgnorePattern removes unescaped trailing spaces and unescapes spaces, +// hashes and bang characters in pattern. +func unescapeIgnorePattern(pattern string) string { + // Trim trailing spaces, unless they are escaped with a backslash + for index := len(pattern) - 1; index > 0 && pattern[index] == ' '; index-- { + if pattern[index-1] != '\\' { + pattern = pattern[:index] + } + } + + // Unescape supported characters + return ignorePatternReplacer.Replace(pattern) +} + +// genIgnorePatternPermutations receives as input a string possibly containing +// one or more double-star separator patterns (/**/) and generates a list of +// additional glob patterns that allow matching zero-or-more items at the +// location of each double-star separator. +// +// For example, given "foo/**/bar/**/baz" as input, this function returns: +// - foo/**/bar/**/baz +// - foo/bar/**/baz +// - foo/**/bar/baz +// - foo/bar/baz +func genIgnorePatternPermutations(in string) []string { + var ( + out []string + remaining = []string{in} + + addedPatternWithoutStars bool + ) + + for len(remaining) != 0 { + next := remaining[0] + remaining = remaining[1:] + + // Split on the double-star separator; stop if no the pattern + // does not contain any more double-star separators. + parts := strings.Split(next, "/**/") + if len(parts) == 1 { + if !addedPatternWithoutStars { + out = append(out, next) + addedPatternWithoutStars = true + } + continue + } + + // Push next to the the out list and append a list of patterns + // to the remain list for the next run by sequentially + // substituting each double star pattern with a slash. For + // example if next is "a/**/b/**c" the generated patterns will + // be: + // - a/b/**/c + // - a/**/b/c + out = append(out, next) + for i := 1; i < len(parts); i++ { + remaining = append( + remaining, + strings.Join(parts[:i], "/**/")+"/"+strings.Join(parts[i:], "/**/"), + ) + } + } + + return out +} diff --git a/internal/charm/jujuignore_test.go b/internal/charm/jujuignore_test.go new file mode 100644 index 00000000000..01950d5fd45 --- /dev/null +++ b/internal/charm/jujuignore_test.go @@ -0,0 +1,227 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "strings" + + gc "gopkg.in/check.v1" +) + +type JujuIgnoreSuite struct{} + +var _ = gc.Suite(&JujuIgnoreSuite{}) + +func (s *JujuIgnoreSuite) TestBuildRules(c *gc.C) { + type test struct { + path string + isDir bool + expMatch bool + } + + specs := []struct { + descr string + rules string + tests []test + }{ + { + descr: `Match a directory named "target" at any depth`, + rules: `target/`, + tests: []test{ + {path: "target", isDir: true, expMatch: true}, + {path: "foo/target", isDir: true, expMatch: true}, + {path: "foo/1target", isDir: true, expMatch: false}, + {path: "foo/target", isDir: false, expMatch: false}, + }, + }, + { + descr: `Match a directory OR a file named "target" at any depth`, + rules: `target`, + tests: []test{ + {path: "/target", isDir: true, expMatch: true}, + {path: "/foo/target", isDir: true, expMatch: true}, + {path: "/foo/1target", isDir: true, expMatch: false}, + {path: "/foo/target", isDir: false, expMatch: true}, + }, + }, + { + descr: `Match a directory at the root only`, + rules: `/target/`, + tests: []test{ + {path: "/target", isDir: true, expMatch: true}, + {path: "/foo/target", isDir: true, expMatch: false}, + {path: "/target", isDir: false, expMatch: false}, + }, + }, + { + descr: `Match a directory OR file at the root only`, + rules: `/target`, + tests: []test{ + {path: "/target", isDir: true, expMatch: true}, + {path: "/foo/target", isDir: true, expMatch: false}, + {path: "/target", isDir: false, expMatch: true}, + }, + }, + { + descr: `Every file or dir ending with .go recursively`, + rules: `*.go`, + tests: []test{ + {path: "/target.go", isDir: true, expMatch: true}, + {path: "/target.go", isDir: false, expMatch: true}, + {path: "/foo/target.go", isDir: true, expMatch: true}, + {path: "/foo/target.go", isDir: false, expMatch: true}, + {path: "/target.goT", isDir: true, expMatch: false}, + {path: "/target.goT", isDir: false, expMatch: false}, + }, + }, + { + descr: `every file or dir named "#comment"`, + rules: ` +# NOTE: leading hash must be escaped so as not to treat line as a comment +\#comment +`, + tests: []test{ + {path: "/#comment", isDir: true, expMatch: true}, + {path: "/#comment", isDir: false, expMatch: true}, + }, + }, + { + descr: `Every dir called "logs" under "apps"`, + rules: `apps/logs/`, + tests: []test{ + {path: "/apps/logs", isDir: true, expMatch: true}, + {path: "/apps/foo/logs", isDir: true, expMatch: false}, + }, + }, + { + descr: `Every dir called "logs" two levels under "apps"`, + rules: `apps/*/logs/`, + tests: []test{ + {path: "/apps/foo/logs", isDir: true, expMatch: true}, + {path: "/apps/foo/bar/logs", isDir: true, expMatch: false}, + {path: "/apps/logs", isDir: true, expMatch: false}, + }, + }, + { + descr: `Every dir called "logs" any number of levels under "apps"`, + rules: `apps/**/logs/`, + tests: []test{ + {path: "/apps/foo/logs", isDir: true, expMatch: true}, + {path: "/apps/foo/bar/logs", isDir: true, expMatch: true}, + {path: "/apps/logs", isDir: true, expMatch: true}, + }, + }, + { + descr: `Ignore all under "foo" but not "foo" itself`, + rules: `foo/**`, + tests: []test{ + {path: "/foo", isDir: true, expMatch: false}, + {path: "/foo/something", isDir: true, expMatch: true}, + {path: "/foo/something.txt", isDir: false, expMatch: true}, + }, + }, + { + descr: `Ignore all under "foo" except README.md`, + rules: ` +foo/** +!foo/README.md +`, + tests: []test{ + {path: "/foo", isDir: true, expMatch: false}, + {path: "/foo/something", isDir: true, expMatch: true}, + {path: "/foo/something.txt", isDir: false, expMatch: true}, + {path: "/foo/README.md", isDir: false, expMatch: false}, + }, + }, + { + descr: `Multiple double-star separators`, + rules: `foo/**/bar/**/baz`, + tests: []test{ + {path: "/foo/1/2/bar/baz", isDir: true, expMatch: true}, + {path: "/foo/1/2/bar/1/2/baz", isDir: true, expMatch: true}, + {path: "/foo/bar/1/baz", isDir: true, expMatch: true}, + {path: "/foo/1/bar/baz", isDir: true, expMatch: true}, + {path: "/foo/bar/baz", isDir: true, expMatch: true}, + }, + }, + } + + for specIndex, spec := range specs { + c.Logf("[spec %d] %s", specIndex, spec.descr) + + rs, err := newIgnoreRuleset(strings.NewReader(spec.rules)) + if err != nil { + c.Assert(err, gc.IsNil) + continue + } + + for testIndex, test := range spec.tests { + c.Logf(" [test %d] match against path %q", testIndex, test.path) + c.Assert(rs.Match(test.path, test.isDir), gc.DeepEquals, test.expMatch) + } + } +} + +func (s *JujuIgnoreSuite) TestGenIgnorePatternPermutations(c *gc.C) { + specs := []struct { + in string + exp []string + }{ + { + in: "foo/bar", + exp: []string{"foo/bar"}, + }, + { + in: "foo/**/bar", + exp: []string{ + "foo/**/bar", + "foo/bar", + }, + }, + { + in: "foo/**/bar/**/baz", + exp: []string{ + "foo/**/bar/**/baz", + "foo/bar/**/baz", + "foo/**/bar/baz", + "foo/bar/baz", + }, + }, + } + + for specIndex, spec := range specs { + c.Logf(" [spec %d] gen permutations for %q", specIndex, spec.in) + got := genIgnorePatternPermutations(spec.in) + c.Assert(got, gc.DeepEquals, spec.exp) + } +} + +func (s *JujuIgnoreSuite) TestUnescapeIgnorePattern(c *gc.C) { + specs := []struct { + desc string + in string + exp string + }{ + { + desc: "trailing unescaped spaces should be trimmed", + in: `lore\ m\ `, + exp: `lore m `, + }, + { + desc: "escaped hashes should be unescaped", + in: `\#this-is-not-a-comment`, + exp: `#this-is-not-a-comment`, + }, + { + desc: "escaped bangs should be unescaped", + in: `\!important`, + exp: `!important`, + }, + } + + for specIndex, spec := range specs { + c.Logf("[spec %d] %s", specIndex, spec.desc) + c.Assert(unescapeIgnorePattern(spec.in), gc.Equals, spec.exp) + } +} diff --git a/internal/charm/lxdprofile.go b/internal/charm/lxdprofile.go new file mode 100644 index 00000000000..20a468cdd8d --- /dev/null +++ b/internal/charm/lxdprofile.go @@ -0,0 +1,80 @@ +// Copyright 2018 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "io" + "io/ioutil" + "strings" + + "github.com/juju/collections/set" + "github.com/juju/errors" + "gopkg.in/yaml.v2" +) + +// LXDProfiler defines a way to access a LXDProfile from a charm. +type LXDProfiler interface { + // LXDProfile returns the LXDProfile found in lxd-profile.yaml of the charm + LXDProfile() *LXDProfile +} + +// LXDProfile is the same as ProfilePut defined in github.com/lxc/lxd/shared/api/profile.go +type LXDProfile struct { + Config map[string]string `json:"config" yaml:"config"` + Description string `json:"description" yaml:"description"` + Devices map[string]map[string]string `json:"devices" yaml:"devices"` +} + +// NewLXDProfile creates a LXDProfile with the internal data structures +// initialised to non nil values. +func NewLXDProfile() *LXDProfile { + return &LXDProfile{ + Config: map[string]string{}, + Devices: map[string]map[string]string{}, + } +} + +// ReadLXDProfile reads in a LXDProfile from a charm's lxd-profile.yaml. +// It is not validated at this point so that the caller can choose to override +// any validation. +func ReadLXDProfile(r io.Reader) (*LXDProfile, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + profile := NewLXDProfile() + if err := yaml.Unmarshal(data, profile); err != nil { + return nil, errors.Annotate(err, "failed to unmarshall lxd-profile.yaml") + } + return profile, nil +} + +// ValidateConfigDevices validates the Config and Devices properties of the LXDProfile. +// WhiteList devices: unix-char, unix-block, gpu, usb. +// BlackList config: boot*, limits* and migration*. +// An empty profile will not return an error. +func (profile *LXDProfile) ValidateConfigDevices() error { + for _, val := range profile.Devices { + goodDevs := set.NewStrings("unix-char", "unix-block", "gpu", "usb") + if devType, ok := val["type"]; ok { + if !goodDevs.Contains(devType) { + return fmt.Errorf("invalid lxd-profile.yaml: contains device type %q", devType) + } + } + } + for key := range profile.Config { + if strings.HasPrefix(key, "boot") || + strings.HasPrefix(key, "limits") || + strings.HasPrefix(key, "migration") { + return fmt.Errorf("invalid lxd-profile.yaml: contains config value %q", key) + } + } + return nil +} + +// Empty returns true if neither devices nor config have been defined in the profile. +func (profile *LXDProfile) Empty() bool { + return len(profile.Devices) < 1 && len(profile.Config) < 1 +} diff --git a/internal/charm/lxdprofile_test.go b/internal/charm/lxdprofile_test.go new file mode 100644 index 00000000000..2a28e95a31e --- /dev/null +++ b/internal/charm/lxdprofile_test.go @@ -0,0 +1,164 @@ +// Copyright 2018 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "strings" + + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +type ProfileSuite struct{} + +var _ = gc.Suite(&ProfileSuite{}) + +func (s *ProfileSuite) TestValidate(c *gc.C) { + var profileTests = []struct { + description string + profile *charm.LXDProfile + expectedError string + }{ + { + description: "success", + profile: &charm.LXDProfile{ + Config: map[string]string{ + "security.nesting": "true", + "security.privileged": "true", + "linux.kernel_modules": "openvswitch,ip_tables,ip6_tables", + }, + Description: "success", + Devices: map[string]map[string]string{ + "tun": { + "path": "/dev/net/tun", + "type": "unix-char", + }, + "sony": { + "type": "usb", + "vendorid": "0fce", + "productid": "51da", + }, + "bdisk": { + "type": "unix-block", + "source": "/dev/loop0", + }, + "gpu": { + "type": "gpu", + }, + }, + }, + expectedError: "", + }, { + description: "fail on boot config", + profile: &charm.LXDProfile{ + Config: map[string]string{ + "security.privileged": "true", + "linux.kernel_modules": "openvswitch,ip_tables,ip6_tables", + "boot.autostart": "true", + }, + }, + expectedError: "invalid lxd-profile.yaml: contains config value \"boot.autostart\"", + }, { + description: "fail on limits config", + profile: &charm.LXDProfile{ + Config: map[string]string{ + "security.privileged": "true", + "linux.kernel_modules": "openvswitch,ip_tables,ip6_tables", + "limits.memory": "256MB", + }, + }, + expectedError: "invalid lxd-profile.yaml: contains config value \"limits.memory\"", + }, { + description: "fail on migration config", + profile: &charm.LXDProfile{ + Config: map[string]string{ + "security.privileged": "true", + "linux.kernel_modules": "openvswitch,ip_tables,ip6_tables", + "migration.incremental.memory": "true", + }, + }, + expectedError: "invalid lxd-profile.yaml: contains config value \"migration.incremental.memory\"", + }, { + description: "fail on unix-disk device", + profile: &charm.LXDProfile{ + Config: map[string]string{ + "security.privileged": "true", + "linux.kernel_modules": "openvswitch,ip_tables,ip6_tables", + }, + Devices: map[string]map[string]string{ + "bdisk": { + "type": "unix-disk", + "source": "/dev/loop0", + }, + }, + }, + expectedError: "invalid lxd-profile.yaml: contains device type \"unix-disk\"", + }, + } + for i, test := range profileTests { + c.Logf("test %d: %s", i, test.description) + err := test.profile.ValidateConfigDevices() + if err != nil { + c.Assert(err.Error(), gc.Equals, test.expectedError) + } else { + + c.Assert(err, jc.ErrorIsNil) + } + } + +} + +func (s *ProfileSuite) TestReadLXDProfile(c *gc.C) { + profile, err := charm.ReadLXDProfile(strings.NewReader(` +config: + security.nesting: "true" + security.privileged: "true" + linux.kernel_modules: openvswitch,nbd,ip_tables,ip6_tables +devices: + kvm: + path: /dev/kvm + type: unix-char + mem: + path: /dev/mem + type: unix-char + tun: + path: /dev/net/tun + type: unix-char +`)) + c.Assert(err, jc.ErrorIsNil) + c.Assert(profile, gc.NotNil) +} + +func (s *ProfileSuite) TestLXDProfileEmptyFile(c *gc.C) { + profile, err := charm.ReadLXDProfile(strings.NewReader(` + +`)) + c.Assert(profile, gc.DeepEquals, charm.NewLXDProfile()) + c.Assert(err, jc.ErrorIsNil) + c.Assert(profile.Empty(), jc.IsTrue) + c.Assert(profile.ValidateConfigDevices(), jc.ErrorIsNil) +} + +func (s *ProfileSuite) TestReadLXDProfileFailUnmarshall(c *gc.C) { + profile, err := charm.ReadLXDProfile(strings.NewReader(` +config: + security.nesting: "true" + security.privileged: "true" + linux.kernel_modules: openvswitch,nbd,ip_tables,ip6_tables + devices: + kvm: + path: /dev/kvm + type: unix-char + mem: + path: /dev/mem + type: unix-char + tun: + path: /dev/net/tun + type: unix-char +`)) + c.Assert(err, gc.ErrorMatches, "failed to unmarshall lxd-profile.yaml: yaml: .*") + c.Assert(profile, gc.IsNil) +} diff --git a/internal/charm/manifest.go b/internal/charm/manifest.go new file mode 100644 index 00000000000..9b034060406 --- /dev/null +++ b/internal/charm/manifest.go @@ -0,0 +1,127 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "io" + "io/ioutil" + + "github.com/juju/errors" + "github.com/juju/schema" + "github.com/juju/utils/v4/arch" + "gopkg.in/yaml.v2" +) + +// Manifest represents the recording of the building of the charm or bundle. +// The manifest file should represent the metadata.yaml, but a lot more +// information. +type Manifest struct { + Bases []Base `yaml:"bases"` +} + +// Validate checks the manifest to ensure there are no empty names, nor channels, +// and that architectures are supported. +func (m *Manifest) Validate() error { + for _, b := range m.Bases { + if err := b.Validate(); err != nil { + return errors.Annotate(err, "validating manifest") + } + } + return nil +} + +func (m *Manifest) UnmarshalYAML(f func(interface{}) error) error { + raw := make(map[interface{}]interface{}) + err := f(&raw) + if err != nil { + return err + } + + v, err := schema.List(baseSchema).Coerce(raw["bases"], nil) + if err != nil { + return errors.Annotatef(err, "coerce") + } + + newV, ok := v.([]interface{}) + if !ok { + return errors.Annotatef(err, "converting") + } + bases, err := parseBases(newV) + if err != nil { + return err + } + + *m = Manifest{Bases: bases} + return nil +} + +func parseBases(input interface{}) ([]Base, error) { + var err error + if input == nil { + return nil, nil + } + var res []Base + for _, v := range input.([]interface{}) { + var base Base + baseMap := v.(map[string]interface{}) + if value, ok := baseMap["name"]; ok { + base.Name = value.(string) + } + if value, ok := baseMap["channel"]; ok { + base.Channel, err = ParseChannelNormalize(value.(string)) + if err != nil { + return nil, errors.Annotatef(err, "parsing channel %q", value.(string)) + } + } + base.Architectures = parseArchitectureList(baseMap["architectures"]) + err = base.Validate() + if err != nil { + return nil, errors.Trace(err) + } + res = append(res, base) + } + return res, nil +} + +// ReadManifest reads in a Manifest from a charm's manifest.yaml. Some of +// validation is done when unmarshalling the manifest, including +// verification that the base.Name is a supported operating system. Full +// validation done by calling Validate(). +func ReadManifest(r io.Reader) (*Manifest, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + var manifest *Manifest + if err := yaml.Unmarshal(data, &manifest); err != nil { + return nil, errors.Annotatef(err, "manifest") + } + if manifest == nil { + return nil, errors.Annotatef(err, "invalid base in manifest") + } + return manifest, nil +} + +var baseSchema = schema.FieldMap( + schema.Fields{ + "name": schema.String(), + "channel": schema.String(), + "architectures": schema.List(schema.String()), + }, schema.Defaults{ + "name": schema.Omit, + "channel": schema.Omit, + "architectures": schema.Omit, + }) + +func parseArchitectureList(list interface{}) []string { + if list == nil { + return nil + } + slice := list.([]interface{}) + result := make([]string, 0, len(slice)) + for _, elem := range slice { + result = append(result, arch.NormaliseArch(elem.(string))) + } + return result +} diff --git a/internal/charm/manifest_test.go b/internal/charm/manifest_test.go new file mode 100644 index 00000000000..82ec93f4ea5 --- /dev/null +++ b/internal/charm/manifest_test.go @@ -0,0 +1,64 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package charm + +import ( + "strings" + + "github.com/juju/testing" + gc "gopkg.in/check.v1" +) + +type manifestSuite struct { + testing.CleanupSuite +} + +var _ = gc.Suite(&manifestSuite{}) + +func (s *manifestSuite) TestReadManifest(c *gc.C) { + manifest, err := ReadManifest(strings.NewReader(` +bases: + - name: ubuntu + channel: "18.04" + architectures: ["amd64","aarch64","s390x"] + - name: ubuntu + channel: "20.04/stable" +`)) + c.Assert(err, gc.IsNil) + c.Assert(manifest, gc.DeepEquals, &Manifest{Bases: []Base{{ + Name: "ubuntu", + Channel: Channel{ + Track: "18.04", + Risk: "stable", + Branch: "", + }, + Architectures: []string{"amd64", "arm64", "s390x"}, + }, { + Name: "ubuntu", + Channel: Channel{ + Track: "20.04", + Risk: "stable", + Branch: "", + }, + }, + }}) +} + +func (s *manifestSuite) TestReadValidateManifest(c *gc.C) { + _, err := ReadManifest(strings.NewReader(` +bases: + - name: "" + channel: "18.04" +`)) + c.Assert(err, gc.ErrorMatches, "manifest: base without name not valid") +} + +func (s *manifestSuite) TestValidateManifest(c *gc.C) { + manifest := &Manifest{ + Bases: []Base{{ + Name: "", + }}, + } + c.Assert(manifest.Validate(), gc.ErrorMatches, "validating manifest: base without name not valid") +} diff --git a/internal/charm/meta.go b/internal/charm/meta.go new file mode 100644 index 00000000000..e7d910e949f --- /dev/null +++ b/internal/charm/meta.go @@ -0,0 +1,1513 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + "io" + "io/ioutil" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/juju/collections/set" + "github.com/juju/errors" + "github.com/juju/names/v5" + "github.com/juju/os/v2" + "github.com/juju/os/v2/series" + "github.com/juju/schema" + "github.com/juju/utils/v4" + "github.com/juju/version/v2" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/internal/charm/assumes" + "github.com/juju/juju/internal/charm/hooks" + "github.com/juju/juju/internal/charm/resource" +) + +// RelationScope describes the scope of a relation. +type RelationScope string + +// Note that schema doesn't support custom string types, +// so when we use these values in a schema.Checker, +// we must store them as strings, not RelationScopes. + +const ( + ScopeGlobal RelationScope = "global" + ScopeContainer RelationScope = "container" +) + +// RelationRole defines the role of a relation. +type RelationRole string + +const ( + RoleProvider RelationRole = "provider" + RoleRequirer RelationRole = "requirer" + RolePeer RelationRole = "peer" +) + +// StorageType defines a storage type. +type StorageType string + +const ( + StorageBlock StorageType = "block" + StorageFilesystem StorageType = "filesystem" +) + +// Storage represents a charm's storage requirement. +type Storage struct { + // Name is the name of the store. + // + // Name has no default, and must be specified. + Name string `bson:"name"` + + // Description is a description of the store. + // + // Description has no default, and is optional. + Description string `bson:"description"` + + // Type is the storage type: filesystem or block-device. + // + // Type has no default, and must be specified. + Type StorageType `bson:"type"` + + // Shared indicates that the storage is shared between all units of + // an application deployed from the charm. It is an error to attempt to + // assign non-shareable storage to a "shared" storage requirement. + // + // Shared defaults to false. + Shared bool `bson:"shared"` + + // ReadOnly indicates that the storage should be made read-only if + // possible. If the storage cannot be made read-only, Juju will warn + // the user. + // + // ReadOnly defaults to false. + ReadOnly bool `bson:"read-only"` + + // CountMin is the number of storage instances that must be attached + // to the charm for it to be useful; the charm will not install until + // this number has been satisfied. This must be a non-negative number. + // + // CountMin defaults to 1 for singleton stores. + CountMin int `bson:"countmin"` + + // CountMax is the largest number of storage instances that can be + // attached to the charm. If CountMax is -1, then there is no upper + // bound. + // + // CountMax defaults to 1 for singleton stores. + CountMax int `bson:"countmax"` + + // MinimumSize is the minimum size of store that the charm needs to + // work at all. This is not a recommended size or a comfortable size + // or a will-work-well size, just a bare minimum below which the charm + // is going to break. + // MinimumSize requires a unit, one of MGTPEZY, and is stored as MiB. + // + // There is no default MinimumSize; if left unspecified, a provider + // specific default will be used, typically 1GB for block storage. + MinimumSize uint64 `bson:"minimum-size"` + + // Location is the mount location for filesystem stores. For multi- + // stores, the location acts as the parent directory for each mounted + // store. + // + // Location has no default, and is optional. + Location string `bson:"location,omitempty"` + + // Properties allow the charm author to characterise the relative storage + // performance requirements and sensitivities for each store. + // eg “transient” is used to indicate that non persistent storage is acceptable, + // such as tmpfs or ephemeral instance disks. + // + // Properties has no default, and is optional. + Properties []string `bson:"properties,omitempty"` +} + +// DeviceType defines a device type. +type DeviceType string + +// Device represents a charm's device requirement (GPU for example). +type Device struct { + // Name is the name of the device. + Name string `bson:"name"` + + // Description is a description of the device. + Description string `bson:"description"` + + // Type is the device type. + // currently supported types are + // - gpu + // - nvidia.com/gpu + // - amd.com/gpu + Type DeviceType `bson:"type"` + + // CountMin is the min number of devices that the charm requires. + CountMin int64 `bson:"countmin"` + + // CountMax is the max number of devices that the charm requires. + CountMax int64 `bson:"countmax"` +} + +// DeploymentType defines a deployment type. +type DeploymentType string + +const ( + DeploymentStateless DeploymentType = "stateless" + DeploymentStateful DeploymentType = "stateful" + DeploymentDaemon DeploymentType = "daemon" +) + +// DeploymentMode defines a deployment mode. +type DeploymentMode string + +const ( + ModeOperator DeploymentMode = "operator" + ModeWorkload DeploymentMode = "workload" +) + +// ServiceType defines a service type. +type ServiceType string + +const ( + ServiceCluster ServiceType = "cluster" + ServiceLoadBalancer ServiceType = "loadbalancer" + ServiceExternal ServiceType = "external" + ServiceOmit ServiceType = "omit" +) + +var validServiceTypes = map[os.OSType][]ServiceType{ + os.Kubernetes: { + ServiceCluster, + ServiceLoadBalancer, + ServiceExternal, + ServiceOmit, + }, +} + +// Deployment represents a charm's deployment requirements in the charm +// metadata.yaml file. +type Deployment struct { + DeploymentType DeploymentType `bson:"type"` + DeploymentMode DeploymentMode `bson:"mode"` + ServiceType ServiceType `bson:"service"` + MinVersion string `bson:"min-version"` +} + +// Relation represents a single relation defined in the charm +// metadata.yaml file. +type Relation struct { + Name string `bson:"name"` + Role RelationRole `bson:"role"` + Interface string `bson:"interface"` + Optional bool `bson:"optional"` + Limit int `bson:"limit"` + Scope RelationScope `bson:"scope"` +} + +// ImplementedBy returns whether the relation is implemented by the supplied charm. +func (r Relation) ImplementedBy(ch Charm) bool { + if r.IsImplicit() { + return true + } + var m map[string]Relation + switch r.Role { + case RoleProvider: + m = ch.Meta().Provides + case RoleRequirer: + m = ch.Meta().Requires + case RolePeer: + m = ch.Meta().Peers + default: + panic(errors.Errorf("unknown relation role %q", r.Role)) + } + rel, found := m[r.Name] + if !found { + return false + } + if rel.Interface == r.Interface { + switch r.Scope { + case ScopeGlobal: + return rel.Scope != ScopeContainer + case ScopeContainer: + return true + default: + panic(errors.Errorf("unknown relation scope %q", r.Scope)) + } + } + return false +} + +// IsImplicit returns whether the relation is supplied by juju itself, +// rather than by a charm. +func (r Relation) IsImplicit() bool { + return (r.Name == "juju-info" && + r.Interface == "juju-info" && + r.Role == RoleProvider) +} + +// RunAs defines which user to run a certain process as. +type RunAs string + +const ( + RunAsDefault RunAs = "" + RunAsRoot RunAs = "root" + RunAsSudoer RunAs = "sudoer" + RunAsNonRoot RunAs = "non-root" +) + +// Meta represents all the known content that may be defined +// within a charm's metadata.yaml file. +// Note: Series is serialised for backward compatibility +// as "supported-series" because a previous +// charm version had an incompatible Series field that +// was unused in practice but still serialized. This +// only applies to JSON because Meta has a custom +// YAML marshaller. +type Meta struct { + Name string `bson:"name" json:"Name"` + Summary string `bson:"summary" json:"Summary"` + Description string `bson:"description" json:"Description"` + Subordinate bool `bson:"subordinate" json:"Subordinate"` + Provides map[string]Relation `bson:"provides,omitempty" json:"Provides,omitempty"` + Requires map[string]Relation `bson:"requires,omitempty" json:"Requires,omitempty"` + Peers map[string]Relation `bson:"peers,omitempty" json:"Peers,omitempty"` + ExtraBindings map[string]ExtraBinding `bson:"extra-bindings,omitempty" json:"ExtraBindings,omitempty"` + Categories []string `bson:"categories,omitempty" json:"Categories,omitempty"` + Tags []string `bson:"tags,omitempty" json:"Tags,omitempty"` + Series []string `bson:"series,omitempty" json:"SupportedSeries,omitempty"` + Storage map[string]Storage `bson:"storage,omitempty" json:"Storage,omitempty"` + Devices map[string]Device `bson:"devices,omitempty" json:"Devices,omitempty"` + Deployment *Deployment `bson:"deployment,omitempty" json:"Deployment,omitempty"` + PayloadClasses map[string]PayloadClass `bson:"payloadclasses,omitempty" json:"PayloadClasses,omitempty"` + Resources map[string]resource.Meta `bson:"resources,omitempty" json:"Resources,omitempty"` + Terms []string `bson:"terms,omitempty" json:"Terms,omitempty"` + MinJujuVersion version.Number `bson:"min-juju-version,omitempty" json:"min-juju-version,omitempty"` + + // v2 + Containers map[string]Container `bson:"containers,omitempty" json:"containers,omitempty" yaml:"containers,omitempty"` + Assumes *assumes.ExpressionTree `bson:"assumes,omitempty" json:"assumes,omitempty" yaml:"assumes,omitempty"` + CharmUser RunAs `bson:"charm-user,omitempty" json:"charm-user,omitempty" yaml:"charm-user,omitempty"` +} + +// Container specifies the possible systems it supports and mounts it wants. +type Container struct { + Resource string `bson:"resource,omitempty" json:"resource,omitempty" yaml:"resource,omitempty"` + Mounts []Mount `bson:"mounts,omitempty" json:"mounts,omitempty" yaml:"mounts,omitempty"` + Uid int `bson:"uid,omitempty" json:"uid,omitempty" yaml:"uid,omitempty"` + Gid int `bson:"gid,omitempty" json:"gid,omitempty" yaml:"gid,omitempty"` +} + +// Mount allows a container to mount a storage filesystem from the storage top-level directive. +type Mount struct { + Storage string `bson:"storage,omitempty" json:"storage,omitempty" yaml:"storage,omitempty"` + Location string `bson:"location,omitempty" json:"location,omitempty" yaml:"location,omitempty"` +} + +func generateRelationHooks(relName string, allHooks map[string]bool) { + for _, hookName := range hooks.RelationHooks() { + allHooks[fmt.Sprintf("%s-%s", relName, hookName)] = true + } +} + +func generateContainerHooks(containerName string, allHooks map[string]bool) { + // Containers using pebble trigger workload hooks. + for _, hookName := range hooks.WorkloadHooks() { + allHooks[fmt.Sprintf("%s-%s", containerName, hookName)] = true + } +} + +func generateStorageHooks(storageName string, allHooks map[string]bool) { + for _, hookName := range hooks.StorageHooks() { + allHooks[fmt.Sprintf("%s-%s", storageName, hookName)] = true + } +} + +// Hooks returns a map of all possible valid hooks, taking relations +// into account. It's a map to enable fast lookups, and the value is +// always true. +func (m Meta) Hooks() map[string]bool { + allHooks := make(map[string]bool) + // Unit hooks + for _, hookName := range hooks.UnitHooks() { + allHooks[string(hookName)] = true + } + // Secret hooks + for _, hookName := range hooks.SecretHooks() { + allHooks[string(hookName)] = true + } + // Relation hooks + for hookName := range m.Provides { + generateRelationHooks(hookName, allHooks) + } + for hookName := range m.Requires { + generateRelationHooks(hookName, allHooks) + } + for hookName := range m.Peers { + generateRelationHooks(hookName, allHooks) + } + for storageName := range m.Storage { + generateStorageHooks(storageName, allHooks) + } + for containerName := range m.Containers { + generateContainerHooks(containerName, allHooks) + } + return allHooks +} + +// Used for parsing Categories and Tags. +func parseStringList(list interface{}) []string { + if list == nil { + return nil + } + slice := list.([]interface{}) + result := make([]string, 0, len(slice)) + for _, elem := range slice { + result = append(result, elem.(string)) + } + return result +} + +var validTermName = regexp.MustCompile(`^[a-z](-?[a-z0-9]+)+$`) + +// TermsId represents a single term id. The term can either be owned +// or "public" (meaning there is no owner). +// The Revision starts at 1. Therefore a value of 0 means the revision +// is unset. +type TermsId struct { + Tenant string + Owner string + Name string + Revision int +} + +// Validate returns an error if the Term contains invalid data. +func (t *TermsId) Validate() error { + if t.Tenant != "" && t.Tenant != "cs" { + if !validTermName.MatchString(t.Tenant) { + return errors.Errorf("wrong term tenant format %q", t.Tenant) + } + } + if t.Owner != "" && !names.IsValidUser(t.Owner) { + return errors.Errorf("wrong owner format %q", t.Owner) + } + if !validTermName.MatchString(t.Name) { + return errors.Errorf("wrong term name format %q", t.Name) + } + if t.Revision < 0 { + return errors.Errorf("negative term revision") + } + return nil +} + +// String returns the term in canonical form. +// This would be one of: +// +// tenant:owner/name/revision +// tenant:name +// owner/name/revision +// owner/name +// name/revision +// name +func (t *TermsId) String() string { + id := make([]byte, 0, len(t.Tenant)+1+len(t.Owner)+1+len(t.Name)+4) + if t.Tenant != "" { + id = append(id, t.Tenant...) + id = append(id, ':') + } + if t.Owner != "" { + id = append(id, t.Owner...) + id = append(id, '/') + } + id = append(id, t.Name...) + if t.Revision != 0 { + id = append(id, '/') + id = strconv.AppendInt(id, int64(t.Revision), 10) + } + return string(id) +} + +// ParseTerm takes a termID as a string and parses it into a Term. +// A complete term is in the form: +// tenant:owner/name/revision +// This function accepts partially specified identifiers +// typically in one of the following forms: +// name +// owner/name +// owner/name/27 # Revision 27 +// name/283 # Revision 283 +// cs:owner/name # Tenant cs +func ParseTerm(s string) (*TermsId, error) { + tenant := "" + termid := s + if t := strings.SplitN(s, ":", 2); len(t) == 2 { + tenant = t[0] + termid = t[1] + } + + tokens := strings.Split(termid, "/") + var term TermsId + switch len(tokens) { + case 1: // "name" + term = TermsId{ + Tenant: tenant, + Name: tokens[0], + } + case 2: // owner/name or name/123 + termRevision, err := strconv.Atoi(tokens[1]) + if err != nil { // owner/name + term = TermsId{ + Tenant: tenant, + Owner: tokens[0], + Name: tokens[1], + } + } else { // name/123 + term = TermsId{ + Tenant: tenant, + Name: tokens[0], + Revision: termRevision, + } + } + case 3: // owner/name/123 + termRevision, err := strconv.Atoi(tokens[2]) + if err != nil { + return nil, errors.Errorf("invalid revision number %q %v", tokens[2], err) + } + term = TermsId{ + Tenant: tenant, + Owner: tokens[0], + Name: tokens[1], + Revision: termRevision, + } + default: + return nil, errors.Errorf("unknown term id format %q", s) + } + if err := term.Validate(); err != nil { + return nil, errors.Trace(err) + } + return &term, nil +} + +// ReadMeta reads the content of a metadata.yaml file and returns +// its representation. +// The data has verified as unambiguous, but not validated. +func ReadMeta(r io.Reader) (*Meta, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + var meta Meta + err = yaml.Unmarshal(data, &meta) + if err != nil { + return nil, err + } + return &meta, nil +} + +// UnmarshalYAML +func (meta *Meta) UnmarshalYAML(f func(interface{}) error) error { + raw := make(map[interface{}]interface{}) + err := f(&raw) + if err != nil { + return err + } + + if err := ensureUnambiguousFormat(raw); err != nil { + return err + } + + v, err := charmSchema.Coerce(raw, nil) + if err != nil { + return errors.New("metadata: " + err.Error()) + } + + m := v.(map[string]interface{}) + meta1, err := parseMeta(m) + if err != nil { + return err + } + + *meta = *meta1 + + // Assumes blocks have their own dedicated parser so we need to invoke + // it here and attach the resulting expression tree (if any) to the + // metadata + var assumesBlock = struct { + Assumes *assumes.ExpressionTree `yaml:"assumes"` + }{} + if err := f(&assumesBlock); err != nil { + return err + } + meta.Assumes = assumesBlock.Assumes + + return nil +} + +func parseMeta(m map[string]interface{}) (*Meta, error) { + var meta Meta + var err error + + meta.Name = m["name"].(string) + // Schema decodes as int64, but the int range should be good + // enough for revisions. + meta.Summary = m["summary"].(string) + meta.Description = m["description"].(string) + meta.Provides = parseRelations(m["provides"], RoleProvider) + meta.Requires = parseRelations(m["requires"], RoleRequirer) + meta.Peers = parseRelations(m["peers"], RolePeer) + if meta.ExtraBindings, err = parseMetaExtraBindings(m["extra-bindings"]); err != nil { + return nil, err + } + meta.Categories = parseStringList(m["categories"]) + meta.Tags = parseStringList(m["tags"]) + if subordinate := m["subordinate"]; subordinate != nil { + meta.Subordinate = subordinate.(bool) + } + meta.Series = parseStringList(m["series"]) + meta.Storage = parseStorage(m["storage"]) + meta.Devices = parseDevices(m["devices"]) + meta.Deployment, err = parseDeployment(m["deployment"], meta.Series) + if err != nil { + return nil, err + } + meta.PayloadClasses = parsePayloadClasses(m["payloads"]) + meta.MinJujuVersion, err = parseMinJujuVersion(m["min-juju-version"]) + if err != nil { + return nil, err + } + meta.Terms = parseStringList(m["terms"]) + + meta.Resources, err = parseMetaResources(m["resources"]) + if err != nil { + return nil, err + } + + // v2 parsing + meta.Containers, err = parseContainers(m["containers"], meta.Resources, meta.Storage) + if err != nil { + return nil, errors.Annotatef(err, "parsing containers") + } + meta.CharmUser, err = parseCharmUser(m["charm-user"]) + if err != nil { + return nil, errors.Annotatef(err, "parsing charm-user") + } + return &meta, nil +} + +// MarshalYAML implements yaml.Marshaler (yaml.v2). +// It is recommended to call Check() before calling this method, +// otherwise you make get metadata which is not v1 nor v2 format. +func (m Meta) MarshalYAML() (interface{}, error) { + var minver string + if m.MinJujuVersion != version.Zero { + minver = m.MinJujuVersion.String() + } + + return struct { + Name string `yaml:"name"` + Summary string `yaml:"summary"` + Description string `yaml:"description"` + Provides map[string]marshaledRelation `yaml:"provides,omitempty"` + Requires map[string]marshaledRelation `yaml:"requires,omitempty"` + Peers map[string]marshaledRelation `yaml:"peers,omitempty"` + ExtraBindings map[string]interface{} `yaml:"extra-bindings,omitempty"` + Categories []string `yaml:"categories,omitempty"` + Tags []string `yaml:"tags,omitempty"` + Subordinate bool `yaml:"subordinate,omitempty"` + Series []string `yaml:"series,omitempty"` + Storage map[string]Storage `yaml:"storage,omitempty"` + Devices map[string]Device `yaml:"devices,omitempty"` + Deployment *Deployment `yaml:"deployment,omitempty"` + Terms []string `yaml:"terms,omitempty"` + MinJujuVersion string `yaml:"min-juju-version,omitempty"` + Resources map[string]marshaledResourceMeta `yaml:"resources,omitempty"` + Containers map[string]marshaledContainer `yaml:"containers,omitempty"` + Assumes *assumes.ExpressionTree `yaml:"assumes,omitempty"` + }{ + Name: m.Name, + Summary: m.Summary, + Description: m.Description, + Provides: marshaledRelations(m.Provides), + Requires: marshaledRelations(m.Requires), + Peers: marshaledRelations(m.Peers), + ExtraBindings: marshaledExtraBindings(m.ExtraBindings), + Categories: m.Categories, + Tags: m.Tags, + Subordinate: m.Subordinate, + Series: m.Series, + Storage: m.Storage, + Devices: m.Devices, + Deployment: m.Deployment, + Terms: m.Terms, + MinJujuVersion: minver, + Resources: marshaledResources(m.Resources), + Containers: marshaledContainers(m.Containers), + Assumes: m.Assumes, + }, nil +} + +type marshaledResourceMeta struct { + Path string `yaml:"filename"` // TODO(ericsnow) Change to "path"? + Type string `yaml:"type,omitempty"` + Description string `yaml:"description,omitempty"` +} + +func marshaledResources(rs map[string]resource.Meta) map[string]marshaledResourceMeta { + rs1 := make(map[string]marshaledResourceMeta, len(rs)) + for name, r := range rs { + r1 := marshaledResourceMeta{ + Path: r.Path, + Description: r.Description, + } + if r.Type != resource.TypeFile { + r1.Type = r.Type.String() + } + rs1[name] = r1 + } + return rs1 +} + +func marshaledRelations(relations map[string]Relation) map[string]marshaledRelation { + marshaled := make(map[string]marshaledRelation) + for name, relation := range relations { + marshaled[name] = marshaledRelation(relation) + } + return marshaled +} + +type marshaledRelation Relation + +func (r marshaledRelation) MarshalYAML() (interface{}, error) { + // See calls to ifaceExpander in charmSchema. + var noLimit int + if !r.Optional && r.Limit == noLimit && r.Scope == ScopeGlobal { + // All attributes are default, so use the simple string form of the relation. + return r.Interface, nil + } + mr := struct { + Interface string `yaml:"interface"` + Limit *int `yaml:"limit,omitempty"` + Optional bool `yaml:"optional,omitempty"` + Scope RelationScope `yaml:"scope,omitempty"` + }{ + Interface: r.Interface, + Optional: r.Optional, + } + if r.Limit != noLimit { + mr.Limit = &r.Limit + } + if r.Scope != ScopeGlobal { + mr.Scope = r.Scope + } + return mr, nil +} + +func marshaledExtraBindings(bindings map[string]ExtraBinding) map[string]interface{} { + marshaled := make(map[string]interface{}) + for _, binding := range bindings { + marshaled[binding.Name] = nil + } + return marshaled +} + +type marshaledContainer Container + +func marshaledContainers(c map[string]Container) map[string]marshaledContainer { + marshaled := make(map[string]marshaledContainer) + for k, v := range c { + marshaled[k] = marshaledContainer(v) + } + return marshaled +} + +func (c marshaledContainer) MarshalYAML() (interface{}, error) { + mc := struct { + Resource string `yaml:"resource,omitempty"` + Mounts []Mount `yaml:"mounts,omitempty"` + }{ + Resource: c.Resource, + Mounts: c.Mounts, + } + return mc, nil +} + +// Format of the parsed charm. +type Format int + +// Formats are the different versions of charm metadata supported. +const ( + FormatUnknown Format = iota + FormatV1 Format = iota + FormatV2 Format = iota +) + +// Check checks that the metadata is well-formed. +func (m Meta) Check(format Format, reasons ...FormatSelectionReason) error { + switch format { + case FormatV1: + err := m.checkV1(reasons) + if err != nil { + return errors.Trace(err) + } + case FormatV2: + err := m.checkV2(reasons) + if err != nil { + return errors.Trace(err) + } + default: + return errors.Errorf("unknown format %v", format) + } + + // Check for duplicate or forbidden relation names or interfaces. + names := make(map[string]bool) + checkRelations := func(src map[string]Relation, role RelationRole) error { + for name, rel := range src { + if rel.Name != name { + return errors.Errorf("charm %q has mismatched relation name %q; expected %q", m.Name, rel.Name, name) + } + if rel.Role != role { + return errors.Errorf("charm %q has mismatched role %q; expected %q", m.Name, rel.Role, role) + } + // Container-scoped require relations on subordinates are allowed + // to use the otherwise-reserved juju-* namespace. + if !m.Subordinate || role != RoleRequirer || rel.Scope != ScopeContainer { + if reserved, _ := reservedName(m.Name, name); reserved { + return errors.Errorf("charm %q using a reserved relation name: %q", m.Name, name) + } + } + if role != RoleRequirer { + if reserved, _ := reservedName(m.Name, rel.Interface); reserved { + return errors.Errorf("charm %q relation %q using a reserved interface: %q", m.Name, name, rel.Interface) + } + } + if names[name] { + return errors.Errorf("charm %q using a duplicated relation name: %q", m.Name, name) + } + names[name] = true + } + return nil + } + if err := checkRelations(m.Provides, RoleProvider); err != nil { + return err + } + if err := checkRelations(m.Requires, RoleRequirer); err != nil { + return err + } + if err := checkRelations(m.Peers, RolePeer); err != nil { + return err + } + + if err := validateMetaExtraBindings(m); err != nil { + return errors.Errorf("charm %q has invalid extra bindings: %v", m.Name, err) + } + + // Subordinate charms must have at least one relation that + // has container scope, otherwise they can't relate to the + // principal. + if m.Subordinate { + valid := false + if m.Requires != nil { + for _, relationData := range m.Requires { + if relationData.Scope == ScopeContainer { + valid = true + break + } + } + } + if !valid { + return errors.Errorf("subordinate charm %q lacks \"requires\" relation with container scope", m.Name) + } + } + + for _, series := range m.Series { + if !IsValidSeries(series) { + return errors.Errorf("charm %q declares invalid series: %q", m.Name, series) + } + } + + names = make(map[string]bool) + for name, store := range m.Storage { + if store.Location != "" && store.Type != StorageFilesystem { + return errors.Errorf(`charm %q storage %q: location may not be specified for "type: %s"`, m.Name, name, store.Type) + } + if store.Type == "" { + return errors.Errorf("charm %q storage %q: type must be specified", m.Name, name) + } + if store.CountMin < 0 { + return errors.Errorf("charm %q storage %q: invalid minimum count %d", m.Name, name, store.CountMin) + } + if store.CountMax == 0 || store.CountMax < -1 { + return errors.Errorf("charm %q storage %q: invalid maximum count %d", m.Name, name, store.CountMax) + } + if names[name] { + return errors.Errorf("charm %q storage %q: duplicated storage name", m.Name, name) + } + names[name] = true + } + + names = make(map[string]bool) + for name, device := range m.Devices { + if device.Type == "" { + return errors.Errorf("charm %q device %q: type must be specified", m.Name, name) + } + if device.CountMax >= 0 && device.CountMin >= 0 && device.CountMin > device.CountMax { + return errors.Errorf( + "charm %q device %q: maximum count %d can not be smaller than minimum count %d", + m.Name, name, device.CountMax, device.CountMin) + } + if names[name] { + return errors.Errorf("charm %q device %q: duplicated device name", m.Name, name) + } + names[name] = true + } + + for name, payloadClass := range m.PayloadClasses { + if payloadClass.Name != name { + return errors.Errorf("mismatch on payload class name (%q != %q)", payloadClass.Name, name) + } + if err := payloadClass.Validate(); err != nil { + return err + } + } + + if err := validateMetaResources(m.Resources); err != nil { + return err + } + + for _, term := range m.Terms { + if _, terr := ParseTerm(term); terr != nil { + return errors.Trace(terr) + } + } + + return nil +} + +func (m Meta) checkV1(reasons []FormatSelectionReason) error { + if m.Assumes != nil { + return errors.NotValidf("assumes in metadata v1") + } + if len(m.Containers) != 0 { + if !hasReason(reasons, SelectionManifest) { + return errors.NotValidf("containers without a manifest.yaml") + } + return errors.NotValidf("containers in metadata v1") + } + return nil +} + +func (m Meta) checkV2(reasons []FormatSelectionReason) error { + if len(reasons) == 0 { + return errors.NotValidf("metadata v2 without manifest.yaml") + } + if len(m.Series) != 0 { + if hasReason(reasons, SelectionManifest) { + return errors.NotValidf("metadata v2 manifest.yaml with series slice") + } + return errors.NotValidf("series slice in metadata v2") + } + if m.MinJujuVersion != version.Zero { + return errors.NotValidf("min-juju-version in metadata v2") + } + if m.Deployment != nil { + return errors.NotValidf("deployment in metadata v2") + } + return nil +} + +func hasReason(reasons []FormatSelectionReason, reason FormatSelectionReason) bool { + return set.NewStrings(reasons...).Contains(reason) +} + +func reservedName(charmName, endpointName string) (reserved bool, reason string) { + if strings.HasPrefix(charmName, "juju-") { + return false, "" + } + if endpointName == "juju" { + return true, `"juju" is a reserved name` + } + if strings.HasPrefix(endpointName, "juju-") { + return true, `the "juju-" prefix is reserved` + } + return false, "" +} + +func parseRelations(relations interface{}, role RelationRole) map[string]Relation { + if relations == nil { + return nil + } + result := make(map[string]Relation) + for name, rel := range relations.(map[string]interface{}) { + relMap := rel.(map[string]interface{}) + relation := Relation{ + Name: name, + Role: role, + Interface: relMap["interface"].(string), + Optional: relMap["optional"].(bool), + } + if scope := relMap["scope"]; scope != nil { + relation.Scope = RelationScope(scope.(string)) + } + if relMap["limit"] != nil { + // Schema defaults to int64, but we know + // the int range should be more than enough. + relation.Limit = int(relMap["limit"].(int64)) + } + result[name] = relation + } + return result +} + +// CombinedRelations returns all defined relations, regardless of their type in +// a single map. +func (m Meta) CombinedRelations() map[string]Relation { + combined := make(map[string]Relation) + for name, relation := range m.Provides { + combined[name] = relation + } + for name, relation := range m.Requires { + combined[name] = relation + } + for name, relation := range m.Peers { + combined[name] = relation + } + return combined +} + +// Schema coercer that expands the interface shorthand notation. +// A consistent format is easier to work with than considering the +// potential difference everywhere. +// +// Supports the following variants:: +// +// provides: +// server: riak +// admin: http +// foobar: +// interface: blah +// +// provides: +// server: +// interface: mysql +// limit: +// optional: false +// +// In all input cases, the output is the fully specified interface +// representation as seen in the mysql interface description above. +func ifaceExpander(limit interface{}) schema.Checker { + return ifaceExpC{limit} +} + +type ifaceExpC struct { + limit interface{} +} + +var ( + stringC = schema.String() + mapC = schema.StringMap(schema.Any()) +) + +func (c ifaceExpC) Coerce(v interface{}, path []string) (newv interface{}, err error) { + s, err := stringC.Coerce(v, path) + if err == nil { + newv = map[string]interface{}{ + "interface": s, + "limit": c.limit, + "optional": false, + "scope": string(ScopeGlobal), + } + return + } + + v, err = mapC.Coerce(v, path) + if err != nil { + return + } + m := v.(map[string]interface{}) + if _, ok := m["limit"]; !ok { + m["limit"] = c.limit + } + return ifaceSchema.Coerce(m, path) +} + +var ifaceSchema = schema.FieldMap( + schema.Fields{ + "interface": schema.String(), + "limit": schema.OneOf(schema.Const(nil), schema.Int()), + "scope": schema.OneOf(schema.Const(string(ScopeGlobal)), schema.Const(string(ScopeContainer))), + "optional": schema.Bool(), + }, + schema.Defaults{ + "scope": string(ScopeGlobal), + "optional": false, + }, +) + +func parseStorage(stores interface{}) map[string]Storage { + if stores == nil { + return nil + } + result := make(map[string]Storage) + for name, store := range stores.(map[string]interface{}) { + storeMap := store.(map[string]interface{}) + store := Storage{ + Name: name, + Type: StorageType(storeMap["type"].(string)), + Shared: storeMap["shared"].(bool), + ReadOnly: storeMap["read-only"].(bool), + CountMin: 1, + CountMax: 1, + } + if desc, ok := storeMap["description"].(string); ok { + store.Description = desc + } + if multiple, ok := storeMap["multiple"].(map[string]interface{}); ok { + if r, ok := multiple["range"].([2]int); ok { + store.CountMin, store.CountMax = r[0], r[1] + } + } + if minSize, ok := storeMap["minimum-size"].(uint64); ok { + store.MinimumSize = minSize + } + if loc, ok := storeMap["location"].(string); ok { + store.Location = loc + } + if properties, ok := storeMap["properties"].([]interface{}); ok { + for _, p := range properties { + store.Properties = append(store.Properties, p.(string)) + } + } + result[name] = store + } + return result +} + +func parseDevices(devices interface{}) map[string]Device { + if devices == nil { + return nil + } + result := make(map[string]Device) + for name, device := range devices.(map[string]interface{}) { + deviceMap := device.(map[string]interface{}) + device := Device{ + Name: name, + Type: DeviceType(deviceMap["type"].(string)), + CountMin: 1, + CountMax: 1, + } + if desc, ok := deviceMap["description"].(string); ok { + device.Description = desc + } + if countmin, ok := deviceMap["countmin"].(int64); ok { + device.CountMin = countmin + } + if countmax, ok := deviceMap["countmax"].(int64); ok { + device.CountMax = countmax + } + result[name] = device + } + return result +} + +func parseDeployment(deployment interface{}, charmSeries []string) (*Deployment, error) { + if deployment == nil { + return nil, nil + } + if len(charmSeries) == 0 { + return nil, errors.New("charm with deployment metadata must declare at least one series") + } + if charmSeries[0] != kubernetes { + return nil, errors.Errorf("charms with deployment metadata only supported for %q", kubernetes) + } + deploymentMap := deployment.(map[string]interface{}) + var result Deployment + if deploymentType, ok := deploymentMap["type"].(string); ok { + result.DeploymentType = DeploymentType(deploymentType) + } + if deploymentMode, ok := deploymentMap["mode"].(string); ok { + result.DeploymentMode = DeploymentMode(deploymentMode) + } + if serviceType, ok := deploymentMap["service"].(string); ok { + result.ServiceType = ServiceType(serviceType) + } + if minVersion, ok := deploymentMap["min-version"].(string); ok { + result.MinVersion = minVersion + } + if result.ServiceType != "" { + osForSeries, err := series.GetOSFromSeries(charmSeries[0]) + if err != nil { + return nil, errors.NotValidf("series %q", charmSeries[0]) + } + valid := false + allowed := validServiceTypes[osForSeries] + for _, st := range allowed { + if st == result.ServiceType { + valid = true + break + } + } + if !valid { + return nil, errors.NotValidf("service type %q for OS %q", result.ServiceType, osForSeries) + } + } + return &result, nil +} + +func parseContainers(input interface{}, resources map[string]resource.Meta, storage map[string]Storage) (map[string]Container, error) { + var err error + if input == nil { + return nil, nil + } + containers := map[string]Container{} + for name, v := range input.(map[string]interface{}) { + containerMap := v.(map[string]interface{}) + container := Container{} + + if value, ok := containerMap["resource"]; ok { + container.Resource = value.(string) + } + if container.Resource != "" { + if r, ok := resources[container.Resource]; !ok { + return nil, errors.NotFoundf("referenced resource %q", container.Resource) + } else if r.Type != resource.TypeContainerImage { + return nil, errors.Errorf("referenced resource %q is not a %s", + container.Resource, + resource.TypeContainerImage.String()) + } + } + + container.Mounts, err = parseMounts(containerMap["mounts"], storage) + if err != nil { + return nil, errors.Annotatef(err, "container %q", name) + } + + if value, ok := containerMap["uid"]; ok { + container.Uid = int(value.(int64)) + if container.Uid >= 1000 && container.Uid < 10000 { + return nil, errors.Errorf("container %q has invalid uid %d: uid cannot be in reserved range 1000-9999", + name, container.Uid) + } + } + if value, ok := containerMap["gid"]; ok { + container.Gid = int(value.(int64)) + if container.Gid >= 1000 && container.Gid < 10000 { + return nil, errors.Errorf("container %q has invalid gid %d: gid cannot be in reserved range 1000-9999", + name, container.Gid) + } + } + + containers[name] = container + } + if len(containers) == 0 { + return nil, nil + } + return containers, nil +} + +func parseMounts(input interface{}, storage map[string]Storage) ([]Mount, error) { + if input == nil { + return nil, nil + } + mounts := []Mount(nil) + for _, v := range input.([]interface{}) { + mount := Mount{} + mountMap := v.(map[string]interface{}) + if value, ok := mountMap["storage"].(string); ok { + mount.Storage = value + } + if value, ok := mountMap["location"].(string); ok { + mount.Location = value + } + if mount.Storage == "" { + return nil, errors.Errorf("storage must be specifed on mount") + } + if mount.Location == "" { + return nil, errors.Errorf("location must be specifed on mount") + } + if _, ok := storage[mount.Storage]; !ok { + return nil, errors.NotValidf("storage %q", mount.Storage) + } + mounts = append(mounts, mount) + } + return mounts, nil +} + +func parseMinJujuVersion(value any) (version.Number, error) { + if value == nil { + return version.Zero, nil + } + ver, err := version.Parse(value.(string)) + if err != nil { + return version.Zero, errors.Annotate(err, "invalid min-juju-version") + } + return ver, nil +} + +func parseCharmUser(value any) (RunAs, error) { + if value == nil { + return RunAsDefault, nil + } + v := RunAs(value.(string)) + switch v { + case RunAsRoot, RunAsSudoer, RunAsNonRoot: + return v, nil + default: + return RunAsDefault, errors.Errorf("invalid charm-user %q expected one of %s, %s or %s", v, + RunAsRoot, RunAsSudoer, RunAsNonRoot) + } +} + +var storageSchema = schema.FieldMap( + schema.Fields{ + "type": schema.OneOf(schema.Const(string(StorageBlock)), schema.Const(string(StorageFilesystem))), + "shared": schema.Bool(), + "read-only": schema.Bool(), + "multiple": schema.FieldMap( + schema.Fields{ + "range": storageCountC{}, // m, m-n, m+, m- + }, + schema.Defaults{}, + ), + "minimum-size": storageSizeC{}, + "location": schema.String(), + "description": schema.String(), + "properties": schema.List(propertiesC{}), + }, + schema.Defaults{ + "shared": false, + "read-only": false, + "multiple": schema.Omit, + "location": schema.Omit, + "description": schema.Omit, + "properties": schema.Omit, + "minimum-size": schema.Omit, + }, +) + +var deviceSchema = schema.FieldMap( + schema.Fields{ + "description": schema.String(), + "type": schema.String(), + "countmin": deviceCountC{}, + "countmax": deviceCountC{}, + }, schema.Defaults{ + "description": schema.Omit, + "countmin": schema.Omit, + "countmax": schema.Omit, + }, +) + +type deviceCountC struct{} + +func (c deviceCountC) Coerce(v interface{}, path []string) (interface{}, error) { + s, err := schema.Int().Coerce(v, path) + if err != nil { + return 0, err + } + if m, ok := s.(int64); ok { + if m >= 0 { + return m, nil + } + } + return 0, errors.Errorf("invalid device count %d", s) +} + +type storageCountC struct{} + +var storageCountRE = regexp.MustCompile("^([0-9]+)([-+]|-[0-9]+)$") + +func (c storageCountC) Coerce(v interface{}, path []string) (newv interface{}, err error) { + s, err := schema.OneOf(schema.Int(), stringC).Coerce(v, path) + if err != nil { + return nil, err + } + if m, ok := s.(int64); ok { + // We've got a count of the form "m": m represents + // both the minimum and maximum. + if m <= 0 { + return nil, errors.Errorf("%s: invalid count %v", strings.Join(path[1:], ""), m) + } + return [2]int{int(m), int(m)}, nil + } + match := storageCountRE.FindStringSubmatch(s.(string)) + if match == nil { + return nil, errors.Errorf("%s: value %q does not match 'm', 'm-n', or 'm+'", strings.Join(path[1:], ""), s) + } + var m, n int + if m, err = strconv.Atoi(match[1]); err != nil { + return nil, err + } + if len(match[2]) == 1 { + // We've got a count of the form "m+" or "m-": + // m represents the minimum, and there is no + // upper bound. + n = -1 + } else { + if n, err = strconv.Atoi(match[2][1:]); err != nil { + return nil, err + } + } + return [2]int{m, n}, nil +} + +type storageSizeC struct{} + +func (c storageSizeC) Coerce(v interface{}, path []string) (newv interface{}, err error) { + s, err := schema.String().Coerce(v, path) + if err != nil { + return nil, err + } + return utils.ParseSize(s.(string)) +} + +type propertiesC struct{} + +func (c propertiesC) Coerce(v interface{}, path []string) (newv interface{}, err error) { + return schema.OneOf(schema.Const("transient")).Coerce(v, path) +} + +var deploymentSchema = schema.FieldMap( + schema.Fields{ + "type": schema.OneOf( + schema.Const(string(DeploymentStateful)), + schema.Const(string(DeploymentStateless)), + schema.Const(string(DeploymentDaemon)), + ), + "mode": schema.OneOf( + schema.Const(string(ModeOperator)), + schema.Const(string(ModeWorkload)), + ), + "service": schema.OneOf( + schema.Const(string(ServiceCluster)), + schema.Const(string(ServiceLoadBalancer)), + schema.Const(string(ServiceExternal)), + schema.Const(string(ServiceOmit)), + ), + "min-version": schema.String(), + }, schema.Defaults{ + "type": schema.Omit, + "mode": string(ModeWorkload), + "service": schema.Omit, + "min-version": schema.Omit, + }, +) + +var containerSchema = schema.FieldMap( + schema.Fields{ + "resource": schema.String(), + "mounts": schema.List(mountSchema), + "uid": schema.Int(), + "gid": schema.Int(), + }, schema.Defaults{ + "resource": schema.Omit, + "mounts": schema.Omit, + "uid": schema.Omit, + "gid": schema.Omit, + }) + +var mountSchema = schema.FieldMap( + schema.Fields{ + "storage": schema.String(), + "location": schema.String(), + }, schema.Defaults{ + "storage": schema.Omit, + "location": schema.Omit, + }) + +var charmSchema = schema.FieldMap( + schema.Fields{ + "name": schema.String(), + "summary": schema.String(), + "description": schema.String(), + "peers": schema.StringMap(ifaceExpander(nil)), + "provides": schema.StringMap(ifaceExpander(nil)), + "requires": schema.StringMap(ifaceExpander(nil)), + "extra-bindings": extraBindingsSchema, + "revision": schema.Int(), // Obsolete + "format": schema.Int(), // Obsolete + "subordinate": schema.Bool(), + "categories": schema.List(schema.String()), + "tags": schema.List(schema.String()), + "series": schema.List(schema.String()), + "storage": schema.StringMap(storageSchema), + "devices": schema.StringMap(deviceSchema), + "deployment": deploymentSchema, + "payloads": schema.StringMap(payloadClassSchema), + "resources": schema.StringMap(resourceSchema), + "terms": schema.List(schema.String()), + "min-juju-version": schema.String(), + "assumes": schema.List(schema.Any()), + "containers": schema.StringMap(containerSchema), + "charm-user": schema.String(), + }, + schema.Defaults{ + "provides": schema.Omit, + "requires": schema.Omit, + "peers": schema.Omit, + "extra-bindings": schema.Omit, + "revision": schema.Omit, + "format": schema.Omit, + "subordinate": schema.Omit, + "categories": schema.Omit, + "tags": schema.Omit, + "series": schema.Omit, + "storage": schema.Omit, + "devices": schema.Omit, + "deployment": schema.Omit, + "payloads": schema.Omit, + "resources": schema.Omit, + "terms": schema.Omit, + "min-juju-version": schema.Omit, + "assumes": schema.Omit, + "containers": schema.Omit, + "charm-user": schema.Omit, + }, +) + +// ensureUnambiguousFormat returns an error if the raw data contains +// both metadata v1 and v2 contents. However is it unable to definitively +// determine which format the charm is as metadata does not contain bases. +func ensureUnambiguousFormat(raw map[interface{}]interface{}) error { + format := FormatUnknown + matched := []string(nil) + mismatched := []string(nil) + keys := []string(nil) + for k := range raw { + key, ok := k.(string) + if !ok { + // Non-string keys will be an error handled by the schema lib. + continue + } + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + detected := FormatUnknown + switch key { + case "containers", "assumes", "charm-user": + detected = FormatV2 + case "series", "deployment", "min-juju-version": + detected = FormatV1 + } + if detected == FormatUnknown { + continue + } + if format == FormatUnknown { + format = detected + } + if format == detected { + matched = append(matched, key) + } else { + mismatched = append(mismatched, key) + } + } + if mismatched != nil { + return errors.Errorf("ambiguous metadata: keys %s cannot be used with %s", + `"`+strings.Join(mismatched, `", "`)+`"`, + `"`+strings.Join(matched, `", "`)+`"`) + } + return nil +} diff --git a/internal/charm/meta_test.go b/internal/charm/meta_test.go new file mode 100644 index 00000000000..9fee8907ab3 --- /dev/null +++ b/internal/charm/meta_test.go @@ -0,0 +1,2091 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + jc "github.com/juju/testing/checkers" + "github.com/juju/version/v2" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/assumes" + "github.com/juju/juju/internal/charm/resource" +) + +func repoMeta(c *gc.C, name string) io.Reader { + charmDir := charmDirPath(c, name) + file, err := os.Open(filepath.Join(charmDir, "metadata.yaml")) + c.Assert(err, gc.IsNil) + defer file.Close() + data, err := ioutil.ReadAll(file) + c.Assert(err, gc.IsNil) + return bytes.NewReader(data) +} + +type MetaSuite struct{} + +var _ = gc.Suite(&MetaSuite{}) + +func (s *MetaSuite) TestReadMetaVersion1(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "dummy")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Name, gc.Equals, "dummy") + c.Assert(meta.Summary, gc.Equals, "That's a dummy charm.") + c.Assert(meta.Description, gc.Equals, + "This is a longer description which\npotentially contains multiple lines.\n") + c.Assert(meta.Subordinate, gc.Equals, false) +} + +func (s *MetaSuite) TestReadMetaVersion2(c *gc.C) { + // This checks that we can accept a charm with the + // obsolete "format" field, even though we ignore it. + meta, err := charm.ReadMeta(repoMeta(c, "format2")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Name, gc.Equals, "format2") + c.Assert(meta.Categories, gc.HasLen, 0) + c.Assert(meta.Terms, gc.HasLen, 0) +} + +func (s *MetaSuite) TestValidTermFormat(c *gc.C) { + valid := []string{ + "foobar", + "foobar/27", + "foo/003", + "owner/foobar/27", + "owner/foobar", + "owner/foo-bar", + "own-er/foobar", + "ibm/j9-jvm/2", + "cs:foobar/27", + "cs:foobar", + } + + invalid := []string{ + "/", + "/1", + "//", + "//2", + "27", + "owner/foo/foobar", + "@les/term/1", + "own_er/foobar", + } + + for i, s := range valid { + c.Logf("valid test %d: %s", i, s) + meta := charm.Meta{Terms: []string{s}} + err := meta.Check(charm.FormatV1) + c.Check(err, jc.ErrorIsNil) + } + + for i, s := range invalid { + c.Logf("invalid test %d: %s", i, s) + meta := charm.Meta{Terms: []string{s}} + err := meta.Check(charm.FormatV1) + c.Check(err, gc.NotNil) + } +} + +func (s *MetaSuite) TestTermStringRoundTrip(c *gc.C) { + terms := []string{ + "foobar", + "foobar/27", + "owner/foobar/27", + "owner/foobar", + "owner/foo-bar", + "own-er/foobar", + "ibm/j9-jvm/2", + "cs:foobar/27", + } + for i, term := range terms { + c.Logf("test %d: %s", i, term) + id, err := charm.ParseTerm(term) + c.Check(err, gc.IsNil) + c.Check(id.String(), gc.Equals, term) + } +} + +func (s *MetaSuite) TestCheckTerms(c *gc.C) { + tests := []struct { + about string + terms []string + expectError string + }{{ + about: "valid terms", + terms: []string{"term/1", "term/2", "term-without-revision", "tt/2"}, + }, { + about: "revision not a number", + terms: []string{"term/1", "term/a"}, + expectError: `wrong term name format "a"`, + }, { + about: "negative revision", + terms: []string{"term/-1"}, + expectError: "negative term revision", + }, { + about: "wrong format", + terms: []string{"term/1", "foobar/term/abc/1"}, + expectError: `unknown term id format "foobar/term/abc/1"`, + }, { + about: "term with owner", + terms: []string{"term/1", "term/abc/1"}, + }, { + about: "term with owner no rev", + terms: []string{"term/1", "term/abc"}, + }, { + about: "term may not contain spaces", + terms: []string{"term/1", "term about a term"}, + expectError: `wrong term name format "term about a term"`, + }, { + about: "term name must start with lowercase letter", + terms: []string{"Term/1"}, + expectError: `wrong term name format "Term"`, + }, { + about: "term name cannot contain capital letters", + terms: []string{"owner/foO-Bar"}, + expectError: `wrong term name format "foO-Bar"`, + }, { + about: "term name cannot contain underscores, that's what dashes are for", + terms: []string{"owner/foo_bar"}, + expectError: `wrong term name format "foo_bar"`, + }, { + about: "term name can't end with a dash", + terms: []string{"o-/1"}, + expectError: `wrong term name format "o-"`, + }, { + about: "term name can't contain consecutive dashes", + terms: []string{"o-oo--ooo---o/1"}, + expectError: `wrong term name format "o-oo--ooo---o"`, + }, { + about: "term name more than a single char", + terms: []string{"z/1"}, + expectError: `wrong term name format "z"`, + }, { + about: "term name match the regexp", + terms: []string{"term_123-23aAf/1"}, + expectError: `wrong term name format "term_123-23aAf"`, + }, + } + for i, test := range tests { + c.Logf("running test %v: %v", i, test.about) + meta := charm.Meta{Terms: test.terms} + err := meta.Check(charm.FormatV1) + if test.expectError == "" { + c.Check(err, jc.ErrorIsNil) + } else { + c.Check(err, gc.ErrorMatches, test.expectError) + } + } +} + +func (s *MetaSuite) TestParseTerms(c *gc.C) { + tests := []struct { + about string + term string + expectError string + expectTerm charm.TermsId + }{{ + about: "valid term", + term: "term/1", + expectTerm: charm.TermsId{"", "", "term", 1}, + }, { + about: "valid term no revision", + term: "term", + expectTerm: charm.TermsId{"", "", "term", 0}, + }, { + about: "revision not a number", + term: "term/a", + expectError: `wrong term name format "a"`, + }, { + about: "negative revision", + term: "term/-1", + expectError: "negative term revision", + }, { + about: "bad revision", + term: "owner/term/12a", + expectError: `invalid revision number "12a" strconv.Atoi: parsing "12a": invalid syntax`, + }, { + about: "wrong format", + term: "foobar/term/abc/1", + expectError: `unknown term id format "foobar/term/abc/1"`, + }, { + about: "term with owner", + term: "term/abc/1", + expectTerm: charm.TermsId{"", "term", "abc", 1}, + }, { + about: "term with owner no rev", + term: "term/abc", + expectTerm: charm.TermsId{"", "term", "abc", 0}, + }, { + about: "term may not contain spaces", + term: "term about a term", + expectError: `wrong term name format "term about a term"`, + }, { + about: "term name must not start with a number", + term: "1Term/1", + expectError: `wrong term name format "1Term"`, + }, { + about: "full term with tenant", + term: "tenant:owner/term/1", + expectTerm: charm.TermsId{"tenant", "owner", "term", 1}, + }, { + about: "bad tenant", + term: "tenant::owner/term/1", + expectError: `wrong owner format ":owner"`, + }, { + about: "ownerless term with tenant", + term: "tenant:term/1", + expectTerm: charm.TermsId{"tenant", "", "term", 1}, + }, { + about: "ownerless revisionless term with tenant", + term: "tenant:term", + expectTerm: charm.TermsId{"tenant", "", "term", 0}, + }, { + about: "owner/term with tenant", + term: "tenant:owner/term", + expectTerm: charm.TermsId{"tenant", "owner", "term", 0}, + }, { + about: "term with tenant", + term: "tenant:term", + expectTerm: charm.TermsId{"tenant", "", "term", 0}, + }} + for i, test := range tests { + c.Logf("running test %v: %v", i, test.about) + term, err := charm.ParseTerm(test.term) + if test.expectError == "" { + c.Check(err, jc.ErrorIsNil) + c.Check(term, gc.DeepEquals, &test.expectTerm) + } else { + c.Check(err, gc.ErrorMatches, test.expectError) + c.Check(term, gc.IsNil) + } + } +} + +func (s *MetaSuite) TestReadCategory(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "category")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Categories, jc.DeepEquals, []string{"database"}) +} + +func (s *MetaSuite) TestReadTerms(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "terms")) + c.Assert(err, jc.ErrorIsNil) + err = meta.Check(charm.FormatV1) + c.Assert(err, jc.ErrorIsNil) + c.Assert(meta.Terms, jc.DeepEquals, []string{"term1/1", "term2", "owner/term3/1"}) +} + +var metaDataWithInvalidTermsId = ` +name: terms +summary: "Sample charm with terms and conditions" +description: | + That's a boring charm that requires certain terms. +terms: ["!!!/abc"] +` + +func (s *MetaSuite) TestCheckReadInvalidTerms(c *gc.C) { + reader := strings.NewReader(metaDataWithInvalidTermsId) + meta, err := charm.ReadMeta(reader) + c.Assert(err, jc.ErrorIsNil) + err = meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, `wrong owner format "!!!"`) +} + +func (s *MetaSuite) TestReadTags(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "category")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Tags, jc.DeepEquals, []string{"openstack", "storage"}) +} + +func (s *MetaSuite) TestSubordinate(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "logging")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Subordinate, gc.Equals, true) +} + +func (s *MetaSuite) TestCheckSubordinateWithoutContainerRelation(c *gc.C) { + r := repoMeta(c, "dummy") + hackYaml := ReadYaml(r) + hackYaml["subordinate"] = true + meta, err := charm.ReadMeta(hackYaml.Reader()) + c.Assert(err, jc.ErrorIsNil) + err = meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, "subordinate charm \"dummy\" lacks \"requires\" relation with container scope") +} + +func (s *MetaSuite) TestScopeConstraint(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "logging")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Provides["logging-client"].Scope, gc.Equals, charm.ScopeGlobal) + c.Assert(meta.Requires["logging-directory"].Scope, gc.Equals, charm.ScopeContainer) + c.Assert(meta.Subordinate, gc.Equals, true) +} + +func (s *MetaSuite) TestParseMetaRelations(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "mysql")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Provides["server"], gc.Equals, charm.Relation{ + Name: "server", + Role: charm.RoleProvider, + Interface: "mysql", + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Requires, gc.IsNil) + c.Assert(meta.Peers, gc.IsNil) + + meta, err = charm.ReadMeta(repoMeta(c, "riak")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Provides["endpoint"], gc.Equals, charm.Relation{ + Name: "endpoint", + Role: charm.RoleProvider, + Interface: "http", + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Provides["admin"], gc.Equals, charm.Relation{ + Name: "admin", + Role: charm.RoleProvider, + Interface: "http", + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Peers["ring"], gc.Equals, charm.Relation{ + Name: "ring", + Role: charm.RolePeer, + Interface: "riak", + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Requires, gc.IsNil) + + meta, err = charm.ReadMeta(repoMeta(c, "terracotta")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Provides["dso"], gc.Equals, charm.Relation{ + Name: "dso", + Role: charm.RoleProvider, + Interface: "terracotta", + Optional: true, + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Peers["server-array"], gc.Equals, charm.Relation{ + Name: "server-array", + Role: charm.RolePeer, + Interface: "terracotta-server", + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Requires, gc.IsNil) + + meta, err = charm.ReadMeta(repoMeta(c, "wordpress")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Provides["url"], gc.Equals, charm.Relation{ + Name: "url", + Role: charm.RoleProvider, + Interface: "http", + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Requires["db"], gc.Equals, charm.Relation{ + Name: "db", + Role: charm.RoleRequirer, + Interface: "mysql", + Limit: 1, + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Requires["cache"], gc.Equals, charm.Relation{ + Name: "cache", + Role: charm.RoleRequirer, + Interface: "varnish", + Limit: 2, + Optional: true, + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Peers, gc.IsNil) + + meta, err = charm.ReadMeta(repoMeta(c, "monitoring")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Provides["monitoring-client"], gc.Equals, charm.Relation{ + Name: "monitoring-client", + Role: charm.RoleProvider, + Interface: "monitoring", + Scope: charm.ScopeGlobal, + }) + c.Assert(meta.Requires["monitoring-port"], gc.Equals, charm.Relation{ + Name: "monitoring-port", + Role: charm.RoleRequirer, + Interface: "monitoring", + Scope: charm.ScopeContainer, + }) + c.Assert(meta.Requires["info"], gc.Equals, charm.Relation{ + Name: "info", + Role: charm.RoleRequirer, + Interface: "juju-info", + Scope: charm.ScopeContainer, + }) + + c.Assert(meta.Peers, gc.IsNil) +} + +func (s *MetaSuite) TestCombinedRelations(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "riak")) + c.Assert(err, gc.IsNil) + combinedRelations := meta.CombinedRelations() + expectedLength := len(meta.Provides) + len(meta.Requires) + len(meta.Peers) + c.Assert(combinedRelations, gc.HasLen, expectedLength) + c.Assert(combinedRelations, jc.DeepEquals, map[string]charm.Relation{ + "endpoint": { + Name: "endpoint", + Role: charm.RoleProvider, + Interface: "http", + Scope: charm.ScopeGlobal, + }, + "admin": { + Name: "admin", + Role: charm.RoleProvider, + Interface: "http", + Scope: charm.ScopeGlobal, + }, + "ring": { + Name: "ring", + Role: charm.RolePeer, + Interface: "riak", + Scope: charm.ScopeGlobal, + }, + }) +} + +func (s *MetaSuite) TestParseJujuRelations(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "juju-charm")) + c.Assert(err, gc.IsNil) + c.Assert(meta.Provides["dashboard"], gc.Equals, charm.Relation{ + Name: "dashboard", + Role: charm.RoleProvider, + Interface: "juju-dashboard", + Scope: charm.ScopeGlobal, + }) +} + +var relationsConstraintsTests = []struct { + rels string + err string +}{ + { + "provides:\n foo: ping\nrequires:\n foo: pong", + `charm "a" using a duplicated relation name: "foo"`, + }, { + "requires:\n foo: ping\npeers:\n foo: pong", + `charm "a" using a duplicated relation name: "foo"`, + }, { + "peers:\n foo: ping\nprovides:\n foo: pong", + `charm "a" using a duplicated relation name: "foo"`, + }, { + "provides:\n juju: blob", + `charm "a" using a reserved relation name: "juju"`, + }, { + "requires:\n juju: blob", + `charm "a" using a reserved relation name: "juju"`, + }, { + "peers:\n juju: blob", + `charm "a" using a reserved relation name: "juju"`, + }, { + "provides:\n juju-snap: blub", + `charm "a" using a reserved relation name: "juju-snap"`, + }, { + "requires:\n juju-crackle: blub", + `charm "a" using a reserved relation name: "juju-crackle"`, + }, { + "peers:\n juju-pop: blub", + `charm "a" using a reserved relation name: "juju-pop"`, + }, { + "provides:\n innocuous: juju", + `charm "a" relation "innocuous" using a reserved interface: "juju"`, + }, { + "peers:\n innocuous: juju", + `charm "a" relation "innocuous" using a reserved interface: "juju"`, + }, { + "provides:\n innocuous: juju-snap", + `charm "a" relation "innocuous" using a reserved interface: "juju-snap"`, + }, { + "peers:\n innocuous: juju-snap", + `charm "a" relation "innocuous" using a reserved interface: "juju-snap"`, + }, +} + +func (s *MetaSuite) TestCheckRelationsConstraints(c *gc.C) { + check := func(s, e string) { + meta, err := charm.ReadMeta(strings.NewReader(s)) + c.Assert(err, jc.ErrorIsNil) + c.Assert(meta, gc.NotNil) + err = meta.Check(charm.FormatV1) + if e != "" { + c.Assert(err, gc.ErrorMatches, e) + } else { + c.Assert(err, gc.IsNil) + } + } + prefix := "name: a\nsummary: b\ndescription: c\n" + for i, t := range relationsConstraintsTests { + c.Logf("test %d", i) + check(prefix+t.rels, t.err) + check(prefix+"subordinate: true\n"+t.rels, t.err) + } + // The juju-* namespace is accessible to container-scoped require + // relations on subordinate charms. + check(prefix+` +subordinate: true +requires: + juju-info: + interface: juju-info + scope: container`, "") + // The juju-* interfaces are allowed on any require relation. + check(prefix+` +requires: + innocuous: juju-info`, "") +} + +// dummyMetadata contains a minimally valid charm metadata.yaml +// for testing valid and invalid series. +const dummyMetadata = "name: a\nsummary: b\ndescription: c" + +func (s *MetaSuite) TestSeries(c *gc.C) { + // series not specified + meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata)) + c.Assert(err, gc.IsNil) + c.Check(meta.Series, gc.HasLen, 0) + charmMeta := fmt.Sprintf("%s\nseries:", dummyMetadata) + for _, seriesName := range []string{"precise", "trusty", "plan9"} { + charmMeta = fmt.Sprintf("%s\n - %s", charmMeta, seriesName) + } + meta, err = charm.ReadMeta(strings.NewReader(charmMeta)) + c.Assert(err, gc.IsNil) + c.Assert(meta.Series, gc.DeepEquals, []string{"precise", "trusty", "plan9"}) +} + +func (s *MetaSuite) TestCheckInvalidSeries(c *gc.C) { + for _, seriesName := range []string{"pre-c1se", "pre^cise", "cp/m", "OpenVMS"} { + err := charm.Meta{ + Name: "a", + Summary: "b", + Description: "c", + Series: []string{seriesName}, + }.Check(charm.FormatV1) + c.Check(err, gc.ErrorMatches, `charm "a" declares invalid series: .*`) + } +} + +func (s *MetaSuite) TestMinJujuVersion(c *gc.C) { + // series not specified + meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata)) + c.Assert(err, gc.IsNil) + c.Check(meta.Series, gc.HasLen, 0) + charmMeta := fmt.Sprintf("%s\nmin-juju-version: ", dummyMetadata) + vals := []version.Number{ + {Major: 1, Minor: 25}, + {Major: 1, Minor: 25, Tag: "alpha"}, + {Major: 1, Minor: 25, Patch: 1}, + } + for _, ver := range vals { + val := charmMeta + ver.String() + meta, err = charm.ReadMeta(strings.NewReader(val)) + c.Assert(err, gc.IsNil) + c.Assert(meta.MinJujuVersion, gc.Equals, ver) + } +} + +func (s *MetaSuite) TestInvalidMinJujuVersion(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(dummyMetadata + "\nmin-juju-version: invalid-version")) + + c.Check(err, gc.ErrorMatches, `invalid min-juju-version: invalid version "invalid-version"`) +} + +func (s *MetaSuite) TestNoMinJujuVersion(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata)) + c.Assert(err, jc.ErrorIsNil) + c.Check(meta.MinJujuVersion, gc.Equals, version.Zero) +} + +func (s *MetaSuite) TestCheckMismatchedRelationName(c *gc.C) { + // This Check case cannot be covered by the above + // TestCheckRelationsConstraints tests. + meta := charm.Meta{ + Name: "foo", + Provides: map[string]charm.Relation{ + "foo": { + Name: "foo", + Role: charm.RolePeer, + Interface: "x", + Scope: charm.ScopeGlobal, + }, + }, + } + err := meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, `charm "foo" has mismatched role "peer"; expected "provider"`) +} + +func (s *MetaSuite) TestCheckMismatchedRole(c *gc.C) { + // This Check case cannot be covered by the above + // TestCheckRelationsConstraints tests. + meta := charm.Meta{ + Name: "foo", + Provides: map[string]charm.Relation{ + "foo": { + Role: charm.RolePeer, + Interface: "foo", + Scope: charm.ScopeGlobal, + }, + }, + } + err := meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, `charm "foo" has mismatched relation name ""; expected "foo"`) +} + +func (s *MetaSuite) TestCheckMismatchedExtraBindingName(c *gc.C) { + meta := charm.Meta{ + Name: "foo", + ExtraBindings: map[string]charm.ExtraBinding{ + "foo": {Name: "bar"}, + }, + } + err := meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, `charm "foo" has invalid extra bindings: mismatched extra binding name: got "bar", expected "foo"`) +} + +func (s *MetaSuite) TestCheckEmptyNameKeyOrEmptyExtraBindingName(c *gc.C) { + meta := charm.Meta{ + Name: "foo", + ExtraBindings: map[string]charm.ExtraBinding{"": {Name: "bar"}}, + } + err := meta.Check(charm.FormatV1) + expectedError := `charm "foo" has invalid extra bindings: missing binding name` + c.Assert(err, gc.ErrorMatches, expectedError) + + meta.ExtraBindings = map[string]charm.ExtraBinding{"bar": {Name: ""}} + err = meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, expectedError) +} + +// Test rewriting of a given interface specification into long form. +// +// InterfaceExpander uses `coerce` to do one of two things: +// +// - Rewrite shorthand to the long form used for actual storage +// - Fills in defaults, including a configurable `limit` +// +// This test ensures test coverage on each of these branches, along +// with ensuring the conversion object properly raises SchemaError +// exceptions on invalid data. +func (s *MetaSuite) TestIfaceExpander(c *gc.C) { + e := charm.IfaceExpander(nil) + + path := []string{""} + + // Shorthand is properly rewritten + v, err := e.Coerce("http", path) + c.Assert(err, gc.IsNil) + c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": false, "scope": string(charm.ScopeGlobal)}) + + // Defaults are properly applied + v, err = e.Coerce(map[string]interface{}{"interface": "http"}, path) + c.Assert(err, gc.IsNil) + c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": false, "scope": string(charm.ScopeGlobal)}) + + v, err = e.Coerce(map[string]interface{}{"interface": "http", "limit": 2}, path) + c.Assert(err, gc.IsNil) + c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": int64(2), "optional": false, "scope": string(charm.ScopeGlobal)}) + + v, err = e.Coerce(map[string]interface{}{"interface": "http", "optional": true}, path) + c.Assert(err, gc.IsNil) + c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": true, "scope": string(charm.ScopeGlobal)}) + + // Invalid data raises an error. + _, err = e.Coerce(42, path) + c.Assert(err, gc.ErrorMatches, `: expected map, got int\(42\)`) + + _, err = e.Coerce(map[string]interface{}{"interface": "http", "optional": nil}, path) + c.Assert(err, gc.ErrorMatches, ".optional: expected bool, got nothing") + + _, err = e.Coerce(map[string]interface{}{"interface": "http", "limit": "none, really"}, path) + c.Assert(err, gc.ErrorMatches, ".limit: unexpected value.*") + + // Can change default limit + e = charm.IfaceExpander(1) + v, err = e.Coerce(map[string]interface{}{"interface": "http"}, path) + c.Assert(err, gc.IsNil) + c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": int64(1), "optional": false, "scope": string(charm.ScopeGlobal)}) +} + +func (s *MetaSuite) TestMetaHooks(c *gc.C) { + meta, err := charm.ReadMeta(repoMeta(c, "wordpress")) + c.Assert(err, gc.IsNil) + hooks := meta.Hooks() + expectedHooks := map[string]bool{ + "install": true, + "start": true, + "config-changed": true, + "upgrade-charm": true, + "stop": true, + "remove": true, + "leader-elected": true, + "leader-deposed": true, + "leader-settings-changed": true, + "update-status": true, + "cache-relation-created": true, + "cache-relation-joined": true, + "cache-relation-changed": true, + "cache-relation-departed": true, + "cache-relation-broken": true, + "db-relation-created": true, + "db-relation-joined": true, + "db-relation-changed": true, + "db-relation-departed": true, + "db-relation-broken": true, + "logging-dir-relation-created": true, + "logging-dir-relation-joined": true, + "logging-dir-relation-changed": true, + "logging-dir-relation-departed": true, + "logging-dir-relation-broken": true, + "monitoring-port-relation-created": true, + "monitoring-port-relation-joined": true, + "monitoring-port-relation-changed": true, + "monitoring-port-relation-departed": true, + "monitoring-port-relation-broken": true, + "pre-series-upgrade": true, + "post-series-upgrade": true, + "url-relation-created": true, + "url-relation-joined": true, + "url-relation-changed": true, + "url-relation-departed": true, + "url-relation-broken": true, + "secret-changed": true, + "secret-expired": true, + "secret-remove": true, + "secret-rotate": true, + } + c.Assert(hooks, jc.DeepEquals, expectedHooks) +} + +func (s *MetaSuite) TestCodecRoundTripEmpty(c *gc.C) { + for _, codec := range codecs { + c.Logf("codec %s", codec.Name) + empty_input := charm.Meta{} + data, err := codec.Marshal(empty_input) + c.Assert(err, gc.IsNil) + var empty_output charm.Meta + err = codec.Unmarshal(data, &empty_output) + c.Assert(err, gc.IsNil) + c.Assert(empty_input, jc.DeepEquals, empty_output) + } +} + +func (s *MetaSuite) TestCodecRoundTrip(c *gc.C) { + var input = charm.Meta{ + Name: "Foo", + Summary: "Bar", + Description: "Baz", + Subordinate: true, + Provides: map[string]charm.Relation{ + "qux": { + Name: "qux", + Role: charm.RoleProvider, + Interface: "quxx", + Optional: true, + Limit: 42, + Scope: charm.ScopeGlobal, + }, + }, + Requires: map[string]charm.Relation{ + "frob": { + Name: "frob", + Role: charm.RoleRequirer, + Interface: "quxx", + Optional: true, + Limit: 42, + Scope: charm.ScopeContainer, + }, + }, + Peers: map[string]charm.Relation{ + "arble": { + Name: "arble", + Role: charm.RolePeer, + Interface: "quxx", + Optional: true, + Limit: 42, + Scope: charm.ScopeGlobal, + }, + }, + ExtraBindings: map[string]charm.ExtraBinding{ + "b1": {Name: "b1"}, + "b2": {Name: "b2"}, + }, + Categories: []string{"quxxxx", "quxxxxx"}, + Tags: []string{"openstack", "storage"}, + Terms: []string{"test-term/1", "test-term/2"}, + } + for _, codec := range codecs { + c.Logf("codec %s", codec.Name) + data, err := codec.Marshal(input) + c.Assert(err, gc.IsNil) + var output charm.Meta + err = codec.Unmarshal(data, &output) + c.Assert(err, gc.IsNil) + c.Assert(output, jc.DeepEquals, input, gc.Commentf("data: %q", data)) + } +} + +func (s *MetaSuite) TestCodecRoundTripKubernetes(c *gc.C) { + var input = charm.Meta{ + Name: "Foo", + Summary: "Bar", + Description: "Baz", + Subordinate: true, + Provides: map[string]charm.Relation{ + "qux": { + Name: "qux", + Role: charm.RoleProvider, + Interface: "quxx", + Optional: true, + Limit: 42, + Scope: charm.ScopeGlobal, + }, + }, + Requires: map[string]charm.Relation{ + "frob": { + Name: "frob", + Role: charm.RoleRequirer, + Interface: "quxx", + Optional: true, + Limit: 42, + Scope: charm.ScopeContainer, + }, + }, + Peers: map[string]charm.Relation{ + "arble": { + Name: "arble", + Role: charm.RolePeer, + Interface: "quxx", + Optional: true, + Limit: 42, + Scope: charm.ScopeGlobal, + }, + }, + ExtraBindings: map[string]charm.ExtraBinding{ + "b1": {Name: "b1"}, + "b2": {Name: "b2"}, + }, + Categories: []string{"quxxxx", "quxxxxx"}, + Tags: []string{"openstack", "storage"}, + Terms: []string{"test-term/1", "test-term/2"}, + Containers: map[string]charm.Container{ + "test": { + Mounts: []charm.Mount{{ + Storage: "test", + Location: "/wow/", + }}, + Resource: "test", + }, + }, + Resources: map[string]resource.Meta{ + "test": { + Name: "test", + Type: resource.TypeContainerImage, + }, + "test2": { + Name: "test2", + Type: resource.TypeContainerImage, + }, + }, + Storage: map[string]charm.Storage{ + "test": { + Name: "test", + Type: charm.StorageFilesystem, + CountMin: 1, + CountMax: 1, + }, + }, + } + for _, codec := range codecs { + c.Logf("codec %s", codec.Name) + data, err := codec.Marshal(input) + c.Assert(err, gc.IsNil) + var output charm.Meta + err = codec.Unmarshal(data, &output) + c.Assert(err, gc.IsNil) + c.Assert(output, jc.DeepEquals, input, gc.Commentf("data: %q", data)) + } +} + +var implementedByTests = []struct { + ifce string + name string + role charm.RelationRole + scope charm.RelationScope + match bool + implicit bool +}{ + {"ifce-pro", "pro", charm.RoleProvider, charm.ScopeGlobal, true, false}, + {"blah", "pro", charm.RoleProvider, charm.ScopeGlobal, false, false}, + {"ifce-pro", "blah", charm.RoleProvider, charm.ScopeGlobal, false, false}, + {"ifce-pro", "pro", charm.RoleRequirer, charm.ScopeGlobal, false, false}, + {"ifce-pro", "pro", charm.RoleProvider, charm.ScopeContainer, true, false}, + + {"juju-info", "juju-info", charm.RoleProvider, charm.ScopeGlobal, true, true}, + {"blah", "juju-info", charm.RoleProvider, charm.ScopeGlobal, false, false}, + {"juju-info", "blah", charm.RoleProvider, charm.ScopeGlobal, false, false}, + {"juju-info", "juju-info", charm.RoleRequirer, charm.ScopeGlobal, false, false}, + {"juju-info", "juju-info", charm.RoleProvider, charm.ScopeContainer, true, true}, + + {"ifce-req", "req", charm.RoleRequirer, charm.ScopeGlobal, true, false}, + {"blah", "req", charm.RoleRequirer, charm.ScopeGlobal, false, false}, + {"ifce-req", "blah", charm.RoleRequirer, charm.ScopeGlobal, false, false}, + {"ifce-req", "req", charm.RolePeer, charm.ScopeGlobal, false, false}, + {"ifce-req", "req", charm.RoleRequirer, charm.ScopeContainer, true, false}, + + {"juju-info", "info", charm.RoleRequirer, charm.ScopeContainer, true, false}, + {"blah", "info", charm.RoleRequirer, charm.ScopeContainer, false, false}, + {"juju-info", "blah", charm.RoleRequirer, charm.ScopeContainer, false, false}, + {"juju-info", "info", charm.RolePeer, charm.ScopeContainer, false, false}, + {"juju-info", "info", charm.RoleRequirer, charm.ScopeGlobal, false, false}, + + {"ifce-peer", "peer", charm.RolePeer, charm.ScopeGlobal, true, false}, + {"blah", "peer", charm.RolePeer, charm.ScopeGlobal, false, false}, + {"ifce-peer", "blah", charm.RolePeer, charm.ScopeGlobal, false, false}, + {"ifce-peer", "peer", charm.RoleProvider, charm.ScopeGlobal, false, false}, + {"ifce-peer", "peer", charm.RolePeer, charm.ScopeContainer, true, false}, +} + +func (s *MetaSuite) TestImplementedBy(c *gc.C) { + for i, t := range implementedByTests { + c.Logf("test %d", i) + r := charm.Relation{ + Interface: t.ifce, + Name: t.name, + Role: t.role, + Scope: t.scope, + } + c.Assert(r.ImplementedBy(&dummyCharm{}), gc.Equals, t.match) + c.Assert(r.IsImplicit(), gc.Equals, t.implicit) + } +} + +var metaYAMLMarshalTests = []struct { + about string + yaml string +}{{ + about: "minimal charm", + yaml: ` +name: minimal +description: d +summary: s +`, +}, { + about: "charm with lots of stuff", + yaml: ` +name: big +description: d +summary: s +subordinate: true +provides: + provideSimple: someinterface + provideLessSimple: + interface: anotherinterface + optional: true + scope: container + limit: 3 +requires: + requireSimple: someinterface + requireLessSimple: + interface: anotherinterface + optional: true + scope: container + limit: 3 +peers: + peerSimple: someinterface + peerLessSimple: + interface: peery + optional: true +extra-bindings: + extraBar: + extraFoo1: +categories: [c1, c1] +tags: [t1, t2] +series: + - someseries +resources: + foo: + description: 'a description' + filename: 'x.zip' + bar: + filename: 'y.tgz' + type: file +`, +}, { + about: "minimal charm with nested assumes block", + yaml: ` +name: minimal-with-assumes +description: d +summary: s +assumes: +- chips +- any-of: + - guacamole + - salsa + - any-of: + - good-weather + - great-music +- all-of: + - table + - lazy-suzan +`, +}} + +func (s *MetaSuite) TestYAMLMarshal(c *gc.C) { + for i, test := range metaYAMLMarshalTests { + c.Logf("test %d: %s", i, test.about) + ch, err := charm.ReadMeta(strings.NewReader(test.yaml)) + c.Assert(err, gc.IsNil) + gotYAML, err := yaml.Marshal(ch) + c.Assert(err, gc.IsNil) + gotCh, err := charm.ReadMeta(bytes.NewReader(gotYAML)) + c.Assert(err, gc.IsNil) + c.Assert(gotCh, jc.DeepEquals, ch) + } +} + +func (s *MetaSuite) TestYAMLMarshalSimpleRelationOrExtraBinding(c *gc.C) { + // Check that a simple relation / extra-binding gets marshaled as a string. + chYAML := ` +name: minimal +description: d +summary: s +provides: + server: http +requires: + client: http +peers: + me: http +extra-bindings: + foo: +` + ch, err := charm.ReadMeta(strings.NewReader(chYAML)) + c.Assert(err, gc.IsNil) + gotYAML, err := yaml.Marshal(ch) + c.Assert(err, gc.IsNil) + + var x interface{} + err = yaml.Unmarshal(gotYAML, &x) + c.Assert(err, gc.IsNil) + c.Assert(x, jc.DeepEquals, map[interface{}]interface{}{ + "name": "minimal", + "description": "d", + "summary": "s", + "provides": map[interface{}]interface{}{ + "server": "http", + }, + "requires": map[interface{}]interface{}{ + "client": "http", + }, + "peers": map[interface{}]interface{}{ + "me": "http", + }, + "extra-bindings": map[interface{}]interface{}{ + "foo": nil, + }, + }) +} + +func (s *MetaSuite) TestDevices(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +devices: + bitcoin-miner1: + description: a big gpu device + type: gpu + countmin: 1 + countmax: 1 + bitcoin-miner2: + description: a nvdia gpu device + type: nvidia.com/gpu + countmin: 1 + countmax: 2 + bitcoin-miner3: + description: an amd gpu device + type: amd.com/gpu + countmin: 1 + countmax: 2 +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.Devices, gc.DeepEquals, map[string]charm.Device{ + "bitcoin-miner1": { + Name: "bitcoin-miner1", + Description: "a big gpu device", + Type: "gpu", + CountMin: 1, + CountMax: 1, + }, + "bitcoin-miner2": { + Name: "bitcoin-miner2", + Description: "a nvdia gpu device", + Type: "nvidia.com/gpu", + CountMin: 1, + CountMax: 2, + }, + "bitcoin-miner3": { + Name: "bitcoin-miner3", + Description: "an amd gpu device", + Type: "amd.com/gpu", + CountMin: 1, + CountMax: 2, + }, + }, gc.Commentf("meta: %+v", meta)) +} + +func (s *MetaSuite) TestDevicesDefaultLimitAndRequest(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +devices: + bitcoin-miner: + description: a big gpu device + type: gpu +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.Devices, gc.DeepEquals, map[string]charm.Device{ + "bitcoin-miner": { + Name: "bitcoin-miner", + Description: "a big gpu device", + Type: "gpu", + CountMin: 1, + CountMax: 1, + }, + }, gc.Commentf("meta: %+v", meta)) +} + +func (s *MetaSuite) TestDeployment(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +series: + - kubernetes +deployment: + type: stateless + mode: operator + service: loadbalancer + min-version: "1.15" +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.Deployment, gc.DeepEquals, &charm.Deployment{ + DeploymentType: "stateless", + DeploymentMode: "operator", + ServiceType: "loadbalancer", + MinVersion: "1.15", + }, gc.Commentf("meta: %+v", meta)) +} + +func (s *MetaSuite) TestCheckDeploymentErrors(c *gc.C) { + prefix := ` +name: a +summary: b +description: c +deployment: +`[1:] + + tests := []testErrorPayload{{ + desc: "invalid deployment type", + yaml: " type: foo", + err: `metadata: deployment.type: unexpected value "foo"`, + }, { + desc: "invalid deployment mode", + yaml: " mode: foo", + err: `metadata: deployment.mode: unexpected value "foo"`, + }, { + desc: "invalid service type", + yaml: " service: foo", + err: `metadata: deployment.service: unexpected value "foo"`, + }, { + desc: "invalid service type for series", + yaml: " service: cluster\nseries:\n - xenial", + err: `charms with deployment metadata only supported for "kubernetes"`, + }, { + desc: "missing series", + yaml: " service: cluster", + err: `charm with deployment metadata must declare at least one series`, + }} + + testErrors(c, prefix, tests) +} + +type testErrorPayload struct { + desc string + yaml string + err string +} + +func testErrors(c *gc.C, prefix string, tests []testErrorPayload) { + for i, test := range tests { + c.Logf("test %d: %s", i, test.desc) + c.Logf("\n%s\n", prefix+test.yaml) + _, err := charm.ReadMeta(strings.NewReader(prefix + test.yaml)) + c.Assert(err, gc.ErrorMatches, test.err) + } +} + +func testCheckErrors(c *gc.C, prefix string, tests []testErrorPayload) { + for i, test := range tests { + c.Logf("test %d: %s", i, test.desc) + c.Logf("\n%s\n", prefix+test.yaml) + meta, err := charm.ReadMeta(strings.NewReader(prefix + test.yaml)) + c.Assert(err, jc.ErrorIsNil) + err = meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, test.err) + } +} + +func (s *MetaSuite) TestDevicesErrors(c *gc.C) { + prefix := ` +name: a +summary: b +description: c +devices: + bad-nvidia-gpu: +`[1:] + + tests := []testErrorPayload{{ + desc: "invalid device type", + yaml: " countmin: 0", + err: "metadata: devices.bad-nvidia-gpu.type: expected string, got nothing", + }, { + desc: "countmax has to be greater than 0", + yaml: " countmax: -1\n description: a big gpu device\n type: gpu", + err: "metadata: invalid device count -1", + }, { + desc: "countmin has to be greater than 0", + yaml: " countmin: -1\n description: a big gpu device\n type: gpu", + err: "metadata: invalid device count -1", + }} + + testErrors(c, prefix, tests) + +} + +func (s *MetaSuite) TestCheckDevicesErrors(c *gc.C) { + prefix := ` +name: a +summary: b +description: c +devices: + bad-nvidia-gpu: +`[1:] + + tests := []testErrorPayload{{ + desc: "countmax can not be smaller than countmin", + yaml: " countmin: 2\n countmax: 1\n description: a big gpu device\n type: gpu", + err: "charm \"a\" device \"bad-nvidia-gpu\": maximum count 1 can not be smaller than minimum count 2", + }} + + testCheckErrors(c, prefix, tests) + +} + +func (s *MetaSuite) TestStorage(c *gc.C) { + // "type" is the only required attribute for storage. + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +storage: + store0: + description: woo tee bix + type: block + store1: + type: filesystem +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.Storage, gc.DeepEquals, map[string]charm.Storage{ + "store0": { + Name: "store0", + Description: "woo tee bix", + Type: charm.StorageBlock, + CountMin: 1, // singleton + CountMax: 1, + }, + "store1": { + Name: "store1", + Type: charm.StorageFilesystem, + CountMin: 1, // singleton + CountMax: 1, + }, + }) +} + +func (s *MetaSuite) TestStorageErrors(c *gc.C) { + prefix := ` +name: a +summary: b +description: c +storage: + store-bad: +`[1:] + + tests := []testErrorPayload{{ + desc: "type is required", + yaml: " required: false", + err: "metadata: storage.store-bad.type: unexpected value ", + }, { + desc: "range must be an integer, or integer range (1)", + yaml: " type: filesystem\n multiple:\n range: woat", + err: `metadata: storage.store-bad.multiple.range: value "woat" does not match 'm', 'm-n', or 'm\+'`, + }, { + desc: "range must be an integer, or integer range (2)", + yaml: " type: filesystem\n multiple:\n range: 0-abc", + err: `metadata: storage.store-bad.multiple.range: value "0-abc" does not match 'm', 'm-n', or 'm\+'`, + }, { + desc: "range must be non-negative", + yaml: " type: filesystem\n multiple:\n range: -1", + err: `metadata: storage.store-bad.multiple.range: invalid count -1`, + }, { + desc: "range must be positive", + yaml: " type: filesystem\n multiple:\n range: 0", + err: `metadata: storage.store-bad.multiple.range: invalid count 0`, + }, { + desc: "minimum size must parse correctly", + yaml: " type: block\n minimum-size: foo", + err: `metadata: expected a non-negative number, got "foo"`, + }, { + desc: "minimum size must have valid suffix", + yaml: " type: block\n minimum-size: 10Q", + err: `metadata: invalid multiplier suffix "Q", expected one of MGTPEZY`, + }, { + desc: "properties must contain valid values", + yaml: " type: block\n properties: [transient, foo]", + err: `metadata: .* unexpected value "foo"`, + }} + + testErrors(c, prefix, tests) +} + +func (s *MetaSuite) TestCheckStorageErrors(c *gc.C) { + prefix := ` +name: a +summary: b +description: c +storage: + store-bad: +`[1:] + + tests := []testErrorPayload{{ + desc: "location cannot be specified for block type storage", + yaml: " type: block\n location: /dev/sdc", + err: `charm "a" storage "store-bad": location may not be specified for "type: block"`, + }} + + testCheckErrors(c, prefix, tests) +} + +func (s *MetaSuite) TestStorageCount(c *gc.C) { + testStorageCount := func(count string, min, max int) { + meta, err := charm.ReadMeta(strings.NewReader(fmt.Sprintf(` +name: a +summary: b +description: c +storage: + store0: + type: filesystem + multiple: + range: %s +`, count))) + c.Assert(err, gc.IsNil) + store := meta.Storage["store0"] + c.Assert(store, gc.NotNil) + c.Assert(store.CountMin, gc.Equals, min) + c.Assert(store.CountMax, gc.Equals, max) + } + testStorageCount("1", 1, 1) + testStorageCount("0-1", 0, 1) + testStorageCount("1-1", 1, 1) + testStorageCount("1+", 1, -1) + // n- is equivalent to n+ + testStorageCount("1-", 1, -1) +} + +func (s *MetaSuite) TestStorageLocation(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +storage: + store0: + type: filesystem + location: /var/lib/things +`)) + c.Assert(err, gc.IsNil) + store := meta.Storage["store0"] + c.Assert(store, gc.NotNil) + c.Assert(store.Location, gc.Equals, "/var/lib/things") +} + +func (s *MetaSuite) TestStorageMinimumSize(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +storage: + store0: + type: filesystem + minimum-size: 10G +`)) + c.Assert(err, gc.IsNil) + store := meta.Storage["store0"] + c.Assert(store, gc.NotNil) + c.Assert(store.MinimumSize, gc.Equals, uint64(10*1024)) +} + +func (s *MetaSuite) TestStorageProperties(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +storage: + store0: + type: filesystem + properties: [transient] +`)) + c.Assert(err, gc.IsNil) + store := meta.Storage["store0"] + c.Assert(store, gc.NotNil) + c.Assert(store.Properties, jc.SameContents, []string{"transient"}) +} + +func (s *MetaSuite) TestExtraBindings(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +extra-bindings: + endpoint-1: + foo: + bar-42: +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.ExtraBindings, gc.DeepEquals, map[string]charm.ExtraBinding{ + "endpoint-1": { + Name: "endpoint-1", + }, + "foo": { + Name: "foo", + }, + "bar-42": { + Name: "bar-42", + }, + }) +} + +func (s *MetaSuite) TestExtraBindingsEmptyMapError(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +extra-bindings: +`)) + c.Assert(err, gc.ErrorMatches, "metadata: extra-bindings: expected map, got nothing") + c.Assert(meta, gc.IsNil) +} + +func (s *MetaSuite) TestExtraBindingsNonEmptyValueError(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +extra-bindings: + foo: 42 +`)) + c.Assert(err, gc.ErrorMatches, `metadata: extra-bindings.foo: expected empty value, got int\(42\)`) + c.Assert(meta, gc.IsNil) +} + +func (s *MetaSuite) TestExtraBindingsEmptyNameError(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +extra-bindings: + "": +`)) + c.Assert(err, gc.ErrorMatches, `metadata: extra-bindings: expected non-empty binding name, got string\(""\)`) + c.Assert(meta, gc.IsNil) +} + +func (s *MetaSuite) TestPayloadClasses(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +payloads: + monitor: + type: docker + kvm-guest: + type: kvm +`)) + c.Assert(err, gc.IsNil) + + c.Check(meta.PayloadClasses, jc.DeepEquals, map[string]charm.PayloadClass{ + "monitor": { + Name: "monitor", + Type: "docker", + }, + "kvm-guest": { + Name: "kvm-guest", + Type: "kvm", + }, + }) +} + +func (s *MetaSuite) TestResources(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +resources: + resource-name: + type: file + filename: filename.tgz + description: "One line that is useful when operators need to push it." + other-resource: + type: file + filename: other.zip + image-resource: + type: oci-image + description: "An image" +`)) + c.Assert(err, gc.IsNil) + + c.Check(meta.Resources, jc.DeepEquals, map[string]resource.Meta{ + "resource-name": { + Name: "resource-name", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + "other-resource": { + Name: "other-resource", + Type: resource.TypeFile, + Path: "other.zip", + }, + "image-resource": { + Name: "image-resource", + Type: resource.TypeContainerImage, + Description: "An image", + }, + }) +} + +func (s *MetaSuite) TestParseResourceMetaOkay(c *gc.C) { + name := "my-resource" + data := map[string]interface{}{ + "type": "file", + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + } + res, err := charm.ParseResourceMeta(name, data) + c.Assert(err, jc.ErrorIsNil) + + c.Check(res, jc.DeepEquals, resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }) +} + +func (s *MetaSuite) TestParseResourceMetaMissingName(c *gc.C) { + name := "" + data := map[string]interface{}{ + "type": "file", + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + } + res, err := charm.ParseResourceMeta(name, data) + c.Assert(err, jc.ErrorIsNil) + + c.Check(res, jc.DeepEquals, resource.Meta{ + Name: "", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }) +} + +func (s *MetaSuite) TestParseResourceMetaMissingType(c *gc.C) { + name := "my-resource" + data := map[string]interface{}{ + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + } + res, err := charm.ParseResourceMeta(name, data) + c.Assert(err, jc.ErrorIsNil) + + c.Check(res, jc.DeepEquals, resource.Meta{ + Name: "my-resource", + // Type is the zero value. + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }) +} + +func (s *MetaSuite) TestParseResourceMetaEmptyType(c *gc.C) { + name := "my-resource" + data := map[string]interface{}{ + "type": "", + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + } + _, err := charm.ParseResourceMeta(name, data) + + c.Check(err, gc.ErrorMatches, `unsupported resource type .*`) +} + +func (s *MetaSuite) TestParseResourceMetaUnknownType(c *gc.C) { + name := "my-resource" + data := map[string]interface{}{ + "type": "spam", + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + } + _, err := charm.ParseResourceMeta(name, data) + + c.Check(err, gc.ErrorMatches, `unsupported resource type .*`) +} + +func (s *MetaSuite) TestParseResourceMetaMissingPath(c *gc.C) { + name := "my-resource" + data := map[string]interface{}{ + "type": "file", + "description": "One line that is useful when operators need to push it.", + } + res, err := charm.ParseResourceMeta(name, data) + c.Assert(err, jc.ErrorIsNil) + + c.Check(res, jc.DeepEquals, resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "", + Description: "One line that is useful when operators need to push it.", + }) +} + +func (s *MetaSuite) TestParseResourceMetaMissingComment(c *gc.C) { + name := "my-resource" + data := map[string]interface{}{ + "type": "file", + "filename": "filename.tgz", + } + res, err := charm.ParseResourceMeta(name, data) + c.Assert(err, jc.ErrorIsNil) + + c.Check(res, jc.DeepEquals, resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "", + }) +} + +func (s *MetaSuite) TestParseResourceMetaEmpty(c *gc.C) { + name := "my-resource" + data := make(map[string]interface{}) + res, err := charm.ParseResourceMeta(name, data) + c.Assert(err, jc.ErrorIsNil) + + c.Check(res, jc.DeepEquals, resource.Meta{ + Name: "my-resource", + }) +} + +func (s *MetaSuite) TestParseResourceMetaNil(c *gc.C) { + name := "my-resource" + var data map[string]interface{} + res, err := charm.ParseResourceMeta(name, data) + c.Assert(err, jc.ErrorIsNil) + + c.Check(res, jc.DeepEquals, resource.Meta{ + Name: "my-resource", + }) +} + +func (s *MetaSuite) TestContainers(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +containers: + foo: + resource: test-os + mounts: + - storage: a + location: /b/ + uid: 10 + gid: 10 +resources: + test-os: + type: oci-image +storage: + a: + type: filesystem +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.Containers, jc.DeepEquals, map[string]charm.Container{ + "foo": { + Resource: "test-os", + Mounts: []charm.Mount{{ + Storage: "a", + Location: "/b/", + }}, + Uid: 10, + Gid: 10, + }, + }) +} + +func (s *MetaSuite) TestInvalidUid(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +containers: + foo: + resource: test-os + uid: 1000 +resources: + test-os: + type: oci-image +`)) + c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo" has invalid uid 1000: uid cannot be in reserved range 1000-9999`) +} + +func (s *MetaSuite) TestInvalidGid(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +containers: + foo: + resource: test-os + gid: 1000 +resources: + test-os: + type: oci-image +`)) + c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo" has invalid gid 1000: gid cannot be in reserved range 1000-9999`) +} + +func (s *MetaSuite) TestSystemReferencesFileResource(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +containers: + foo: + resource: test-os + mounts: + - storage: a + location: /b/ +resources: + test-os: + type: file + filename: test.json +storage: + a: + type: filesystem +`)) + c.Assert(err, gc.ErrorMatches, `parsing containers: referenced resource "test-os" is not a oci-image`) +} + +func (s *MetaSuite) TestSystemReferencedMissingResource(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +containers: + foo: + resource: test-os + mounts: + - storage: a + location: /b/ +storage: + a: + type: filesystem +`)) + c.Assert(err, gc.ErrorMatches, `parsing containers: referenced resource "test-os" not found`) +} + +func (s *MetaSuite) TestMountMissingStorage(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +containers: + foo: + resource: test-os + mounts: + - location: /b/ +resources: + test-os: + type: oci-image +storage: + a: + type: filesystem +`)) + c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo": storage must be specifed on mount`) +} + +func (s *MetaSuite) TestMountMissingLocation(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +containers: + foo: + resource: test-os + mounts: + - storage: a +resources: + test-os: + type: oci-image +storage: + a: + type: filesystem +`)) + c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo": location must be specifed on mount`) +} + +func (s *MetaSuite) TestMountIncorrectStorage(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +containers: + foo: + resource: test-os + mounts: + - storage: b + location: /b/ +resources: + test-os: + type: oci-image +storage: + a: + type: filesystem +`)) + c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo": storage "b" not valid`) +} + +func (s *MetaSuite) TestFormatV1AndV2Mixing(c *gc.C) { + _, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +series: + - focal +containers: + foo: + resource: test-os + mounts: + - storage: a + location: /b/ +resources: + test-os: + type: oci-image +storage: + a: + type: filesystem +`)) + c.Assert(err, gc.ErrorMatches, `ambiguous metadata: keys "series" cannot be used with "containers"`) +} + +type dummyCharm struct{} + +func (c *dummyCharm) Version() string { + panic("unused") +} + +func (c *dummyCharm) Config() *charm.Config { + panic("unused") +} + +func (c *dummyCharm) Actions() *charm.Actions { + panic("unused") +} + +func (c *dummyCharm) LXDProfile() *charm.LXDProfile { + panic("unused") +} + +func (c *dummyCharm) Manifest() *charm.Manifest { + panic("unused") +} + +func (c *dummyCharm) Revision() int { + panic("unused") +} + +func (c *dummyCharm) Meta() *charm.Meta { + return &charm.Meta{ + Provides: map[string]charm.Relation{ + "pro": {Interface: "ifce-pro", Scope: charm.ScopeGlobal}, + }, + Requires: map[string]charm.Relation{ + "req": {Interface: "ifce-req", Scope: charm.ScopeGlobal}, + "info": {Interface: "juju-info", Scope: charm.ScopeContainer}, + }, + Peers: map[string]charm.Relation{ + "peer": {Interface: "ifce-peer", Scope: charm.ScopeGlobal}, + }, + } +} + +type FormatMetaSuite struct{} + +var _ = gc.Suite(&FormatMetaSuite{}) + +func (FormatMetaSuite) TestCheckV1(c *gc.C) { + meta := charm.Meta{} + err := meta.Check(charm.FormatV1) + c.Assert(err, jc.ErrorIsNil) +} + +func (FormatMetaSuite) TestCheckV1WithAssumes(c *gc.C) { + meta := charm.Meta{ + Assumes: new(assumes.ExpressionTree), + } + err := meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, `assumes in metadata v1 not valid`) +} + +func (FormatMetaSuite) TestCheckV1WithContainers(c *gc.C) { + meta := charm.Meta{ + Containers: map[string]charm.Container{ + "foo": { + Resource: "test-os", + Mounts: []charm.Mount{{ + Storage: "a", + Location: "/b/", + }}, + }, + }, + } + err := meta.Check(charm.FormatV1) + c.Assert(err, gc.ErrorMatches, `containers without a manifest.yaml not valid`) +} + +func (FormatMetaSuite) TestCheckV1WithContainersWithManifest(c *gc.C) { + meta := charm.Meta{ + Containers: map[string]charm.Container{ + "foo": { + Resource: "test-os", + Mounts: []charm.Mount{{ + Storage: "a", + Location: "/b/", + }}, + }, + }, + } + err := meta.Check(charm.FormatV1, charm.SelectionManifest) + c.Assert(err, gc.ErrorMatches, `containers in metadata v1 not valid`) +} + +func (FormatMetaSuite) TestCheckV2(c *gc.C) { + meta := charm.Meta{} + err := meta.Check(charm.FormatV2, charm.SelectionManifest, charm.SelectionBases) + c.Assert(err, jc.ErrorIsNil) +} + +func (FormatMetaSuite) TestCheckV2NoReasons(c *gc.C) { + meta := charm.Meta{} + err := meta.Check(charm.FormatV2) + c.Assert(err, gc.ErrorMatches, `metadata v2 without manifest.yaml not valid`) +} + +func (FormatMetaSuite) TestCheckV2WithSeries(c *gc.C) { + meta := charm.Meta{ + Series: []string{"bionic"}, + } + err := meta.Check(charm.FormatV2, charm.SelectionManifest, charm.SelectionBases) + c.Assert(err, gc.ErrorMatches, `metadata v2 manifest.yaml with series slice not valid`) +} + +func (FormatMetaSuite) TestCheckV2WithSeriesWithoutManifest(c *gc.C) { + meta := charm.Meta{ + Series: []string{"bionic"}, + } + err := meta.Check(charm.FormatV2, charm.SelectionBases) + c.Assert(err, gc.ErrorMatches, `series slice in metadata v2 not valid`) +} + +func (FormatMetaSuite) TestCheckV2WithMinJujuVersion(c *gc.C) { + meta := charm.Meta{ + MinJujuVersion: version.MustParse("2.0.0"), + } + err := meta.Check(charm.FormatV2, charm.SelectionManifest, charm.SelectionBases) + c.Assert(err, gc.ErrorMatches, `min-juju-version in metadata v2 not valid`) +} + +func (FormatMetaSuite) TestCheckV2WithDeployment(c *gc.C) { + meta := charm.Meta{ + Deployment: &charm.Deployment{}, + } + err := meta.Check(charm.FormatV2, charm.SelectionManifest, charm.SelectionBases) + c.Assert(err, gc.ErrorMatches, `deployment in metadata v2 not valid`) +} + +func (s *MetaSuite) TestCharmUser(c *gc.C) { + meta, err := charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +charm-user: root +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.CharmUser, gc.Equals, charm.RunAsRoot) + + meta, err = charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +charm-user: sudoer +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.CharmUser, gc.Equals, charm.RunAsSudoer) + + meta, err = charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +charm-user: non-root +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.CharmUser, gc.Equals, charm.RunAsNonRoot) + + meta, err = charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +`)) + c.Assert(err, gc.IsNil) + c.Assert(meta.CharmUser, gc.Equals, charm.RunAsDefault) + + _, err = charm.ReadMeta(strings.NewReader(` +name: a +summary: b +description: c +charm-user: barry +`)) + c.Assert(err, gc.ErrorMatches, `parsing charm-user: invalid charm-user "barry" expected one of root, sudoer or non-root`) +} diff --git a/internal/charm/offerurl.go b/internal/charm/offerurl.go new file mode 100644 index 00000000000..fdb24a4fde3 --- /dev/null +++ b/internal/charm/offerurl.go @@ -0,0 +1,171 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. +package charm + +import ( + "fmt" + "regexp" + "strings" + + "github.com/juju/errors" + "github.com/juju/names/v5" +) + +// OfferURL represents the location of an offered application and its +// associated exported endpoints. +type OfferURL struct { + // Source represents where the offer is hosted. + // If empty, the model is another model in the same controller. + Source string // "" or "" or "" + + // User is the user whose namespace in which the offer is made. + // Where a model is specified, the user is the model owner. + User string + + // ModelName is the name of the model providing the exported endpoints. + // It is only used for local URLs or for specifying models in the same + // controller. + ModelName string + + // ApplicationName is the name of the application providing the exported endpoints. + ApplicationName string +} + +// Path returns the path component of the URL. +func (u *OfferURL) Path() string { + var parts []string + if u.User != "" { + parts = append(parts, u.User) + } + if u.ModelName != "" { + parts = append(parts, u.ModelName) + } + path := strings.Join(parts, "/") + path = fmt.Sprintf("%s.%s", path, u.ApplicationName) + if u.Source == "" { + return path + } + return fmt.Sprintf("%s:%s", u.Source, path) +} + +func (u *OfferURL) String() string { + return u.Path() +} + +// AsLocal returns a copy of the URL with an empty (local) source. +func (u *OfferURL) AsLocal() *OfferURL { + localURL := *u + localURL.Source = "" + return &localURL +} + +// HasEndpoint returns whether this offer URL includes an +// endpoint name in the application name. +func (u *OfferURL) HasEndpoint() bool { + return strings.Contains(u.ApplicationName, ":") +} + +// modelApplicationRegexp parses urls of the form controller:user/model.application[:relname] +var modelApplicationRegexp = regexp.MustCompile(`(/?((?P[^/]+)/)?(?P[^.]*)(\.(?P[^:]*(:.*)?))?)?`) + +// IsValidOfferURL ensures that a URL string is a valid OfferURL. +func IsValidOfferURL(urlStr string) bool { + _, err := ParseOfferURL(urlStr) + return err == nil +} + +// ParseOfferURL parses the specified URL string into an OfferURL. +// The URL string is of one of the forms: +// +// . +// .: +// /. +// /.: +// :/. +// :/.: +func ParseOfferURL(urlStr string) (*OfferURL, error) { + return parseOfferURL(urlStr) +} + +// parseOfferURL parses the specified URL string into an OfferURL. +func parseOfferURL(urlStr string) (*OfferURL, error) { + urlParts, err := parseOfferURLParts(urlStr, false) + if err != nil { + return nil, err + } + url := OfferURL(*urlParts) + return &url, nil +} + +// OfferURLParts contains various attributes of a URL. +type OfferURLParts OfferURL + +// ParseOfferURLParts parses a partial URL, filling out what parts are supplied. +// This method is used to generate a filter used to query matching offer URLs. +func ParseOfferURLParts(urlStr string) (*OfferURLParts, error) { + return parseOfferURLParts(urlStr, true) +} + +var endpointRegexp = regexp.MustCompile(`^[a-zA-Z0-9]+$`) + +func maybeParseSource(urlStr string) (source, rest string) { + parts := strings.Split(urlStr, ":") + switch len(parts) { + case 3: + return parts[0], parts[1] + ":" + parts[2] + case 2: + if endpointRegexp.MatchString(parts[1]) { + return "", urlStr + } + return parts[0], parts[1] + } + return "", urlStr +} + +func parseOfferURLParts(urlStr string, allowIncomplete bool) (*OfferURLParts, error) { + var result OfferURLParts + source, urlParts := maybeParseSource(urlStr) + + valid := !strings.HasPrefix(urlStr, ":") + valid = valid && modelApplicationRegexp.MatchString(urlParts) + if valid { + result.Source = source + result.User = modelApplicationRegexp.ReplaceAllString(urlParts, "$user") + result.ModelName = modelApplicationRegexp.ReplaceAllString(urlParts, "$model") + result.ApplicationName = modelApplicationRegexp.ReplaceAllString(urlParts, "$application") + } + if !valid || strings.Contains(result.ModelName, "/") || strings.Contains(result.ApplicationName, "/") { + // TODO(wallyworld) - update error message when we support multi-controller and JAAS CMR + return nil, errors.Errorf("application offer URL has invalid form, must be [.: %q", urlStr) + } + if !allowIncomplete && result.ModelName == "" { + return nil, errors.Errorf("application offer URL is missing model") + } + if !allowIncomplete && result.ApplicationName == "" { + return nil, errors.Errorf("application offer URL is missing application") + } + + // Application name part may contain a relation name part, so strip that bit out + // before validating the name. + appName := strings.Split(result.ApplicationName, ":")[0] + // Validate the resulting URL part values. + if result.User != "" && !names.IsValidUser(result.User) { + return nil, errors.NotValidf("user name %q", result.User) + } + if result.ModelName != "" && !names.IsValidModelName(result.ModelName) { + return nil, errors.NotValidf("model name %q", result.ModelName) + } + if appName != "" && !names.IsValidApplication(appName) { + return nil, errors.NotValidf("application name %q", appName) + } + return &result, nil +} + +// MakeURL constructs an offer URL from the specified components. +func MakeURL(user, model, application, controller string) string { + base := fmt.Sprintf("%s/%s.%s", user, model, application) + if controller == "" { + return base + } + return fmt.Sprintf("%s:%s", controller, base) +} diff --git a/internal/charm/offerurl_test.go b/internal/charm/offerurl_test.go new file mode 100644 index 00000000000..bbbe58b0258 --- /dev/null +++ b/internal/charm/offerurl_test.go @@ -0,0 +1,180 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. +package charm_test + +import ( + "fmt" + "regexp" + "strings" + + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + charm "github.com/juju/juju/internal/charm" +) + +type OfferURLSuite struct{} + +var _ = gc.Suite(&OfferURLSuite{}) + +var offerURLTests = []struct { + s, err string + exact string + url *charm.OfferURL +}{{ + s: "controller:user/modelname.applicationname", + url: &charm.OfferURL{"controller", "user", "modelname", "applicationname"}, +}, { + s: "controller:user/modelname.applicationname:rel", + url: &charm.OfferURL{"controller", "user", "modelname", "applicationname:rel"}, +}, { + s: "modelname.applicationname", + url: &charm.OfferURL{"", "", "modelname", "applicationname"}, +}, { + s: "modelname.applicationname:rel", + url: &charm.OfferURL{"", "", "modelname", "applicationname:rel"}, +}, { + s: "user/modelname.applicationname:rel", + url: &charm.OfferURL{"", "user", "modelname", "applicationname:rel"}, +}, { + s: "/modelname.applicationname", + url: &charm.OfferURL{"", "", "modelname", "applicationname"}, + exact: "modelname.applicationname", +}, { + s: "/modelname.applicationname:rel", + url: &charm.OfferURL{"", "", "modelname", "applicationname:rel"}, + exact: "modelname.applicationname:rel", +}, { + s: "user/modelname.applicationname", + url: &charm.OfferURL{"", "user", "modelname", "applicationname"}, +}, { + s: "controller:modelname", + err: `application offer URL is missing application`, +}, { + s: "controller:user/modelname", + err: `application offer URL is missing application`, +}, { + s: "model", + err: `application offer URL is missing application`, +}, { + s: "/user/model", + err: `application offer URL is missing application`, +}, { + s: "model.application@bad", + err: `application name "application@bad" not valid`, +}, { + s: "user[bad/model.application", + err: `user name "user\[bad" not valid`, +}, { + s: "user/[badmodel.application", + err: `model name "\[badmodel" not valid`, +}} + +func (s *OfferURLSuite) TestParseURL(c *gc.C) { + for i, t := range offerURLTests { + c.Logf("test %d: %q", i, t.s) + url, err := charm.ParseOfferURL(t.s) + + match := t.s + if t.exact != "" { + match = t.exact + } + if t.url != nil { + c.Assert(err, gc.IsNil) + c.Check(url, gc.DeepEquals, t.url) + c.Check(url.String(), gc.Equals, match) + } + if t.err != "" { + t.err = strings.Replace(t.err, "$URL", regexp.QuoteMeta(fmt.Sprintf("%q", t.s)), -1) + c.Check(err, gc.ErrorMatches, t.err) + c.Check(url, gc.IsNil) + } + } +} + +var urlPartsTests = []struct { + s, err string + url *charm.OfferURLParts +}{{ + s: "controller:/user/modelname.applicationname", + url: &charm.OfferURLParts{"controller", "user", "modelname", "applicationname"}, +}, { + s: "user/modelname.applicationname", + url: &charm.OfferURLParts{"", "user", "modelname", "applicationname"}, +}, { + s: "user/modelname", + url: &charm.OfferURLParts{"", "user", "modelname", ""}, +}, { + s: "modelname.application", + url: &charm.OfferURLParts{"", "", "modelname", "application"}, +}, { + s: "controller:/modelname", + url: &charm.OfferURLParts{"controller", "", "modelname", ""}, +}, { + s: "controller:", + url: &charm.OfferURLParts{Source: "controller"}, +}, { + s: "", + url: &charm.OfferURLParts{}, +}, { + s: "user/prod/applicationname/extra", + err: `application offer URL has invalid form, must be \[.: "user/prod/applicationname/extra"`, +}, { + s: "controller:/user/modelname.application@bad", + err: `application name "application@bad" not valid`, +}, { + s: "controller:/user[bad/modelname.application", + err: `user name "user\[bad" not valid`, +}, { + s: ":foo", + err: `application offer URL has invalid form, must be \[.: $URL`, +}} + +func (s *OfferURLSuite) TestParseURLParts(c *gc.C) { + for i, t := range urlPartsTests { + c.Logf("test %d: %q", i, t.s) + url, err := charm.ParseOfferURLParts(t.s) + + if t.url != nil { + c.Check(err, gc.IsNil) + c.Check(url, gc.DeepEquals, t.url) + } + if t.err != "" { + t.err = strings.Replace(t.err, "$URL", regexp.QuoteMeta(fmt.Sprintf("%q", t.s)), -1) + c.Assert(err, gc.ErrorMatches, t.err) + c.Assert(url, gc.IsNil) + } + } +} + +func (s *OfferURLSuite) TestHasEndpoint(c *gc.C) { + url, err := charm.ParseOfferURL("model.application:endpoint") + c.Assert(err, jc.ErrorIsNil) + c.Check(url.HasEndpoint(), jc.IsTrue) + url, err = charm.ParseOfferURL("model.application") + c.Assert(err, jc.ErrorIsNil) + c.Check(url.HasEndpoint(), jc.IsFalse) + url, err = charm.ParseOfferURL("controller:/user/model.application:thing") + c.Assert(err, jc.ErrorIsNil) + c.Check(url.HasEndpoint(), jc.IsTrue) + url, err = charm.ParseOfferURL("controller:/user/model.application") + c.Assert(err, jc.ErrorIsNil) + c.Check(url.HasEndpoint(), jc.IsFalse) +} + +func (s *OfferURLSuite) TestMakeURL(c *gc.C) { + url := charm.MakeURL("user", "model", "app", "") + c.Assert(url, gc.Equals, "user/model.app") + url = charm.MakeURL("user", "model", "app", "ctrl") + c.Assert(url, gc.Equals, "ctrl:user/model.app") +} + +func (s *OfferURLSuite) TestAsLocal(c *gc.C) { + url, err := charm.ParseOfferURL("source:model.application:endpoint") + c.Assert(err, jc.ErrorIsNil) + expected, err := charm.ParseOfferURL("model.application:endpoint") + c.Assert(err, jc.ErrorIsNil) + original := *url + c.Assert(url.AsLocal(), gc.DeepEquals, expected) + c.Assert(*url, gc.DeepEquals, original) +} diff --git a/internal/charm/overlay.go b/internal/charm/overlay.go new file mode 100644 index 00000000000..e5db5b32427 --- /dev/null +++ b/internal/charm/overlay.go @@ -0,0 +1,772 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "encoding/base64" + "fmt" + "math" + "path/filepath" + "reflect" + "strings" + + "github.com/juju/errors" + "github.com/mohae/deepcopy" +) + +// ExtractBaseAndOverlayParts splits the bundle data into a base and +// overlay-specific bundle so that their union yields bd. To decide whether a +// field is overlay-specific, the implementation uses reflection and +// recursively scans the BundleData fields looking for fields annotated with +// the "overlay-only: true" tag. +// +// To produce the base bundle, the original bundle is filtered and all +// overlay-specific values are set to the zero value for their type. To produce +// the overlay-specific bundle, we once again filter the original bundle but +// this time zero out fields that do not contain any descendant fields that are +// overlay-specific. +// +// To clarify how this method works let's consider a bundle created via the +// yaml blob below: +// +// applications: +// apache2: +// charm: cs:apache2-26 +// offers: +// my-offer: +// endpoints: +// - apache-website +// - website-cache +// my-other-offer: +// endpoints: +// - apache-website +// series: bionic +// +// The "offers" and "endpoints" attributes are overlay-specific fields. If we +// were to run this method and then marshal the results back to yaml we would +// get: +// +// The base bundle: +// +// applications: +// apache2: +// charm: cs:apache2-26 +// series: bionic +// +// The overlay-specific bundle: +// +// applications: +// apache2: +// offers: +// my-offer: +// endpoints: +// - apache-website +// - website-cache +// my-other-offer: +// endpoints: +// - apache-website +// +// The two bundles returned by this method are copies of the original bundle +// data and can thus be safely manipulated by the caller. +func ExtractBaseAndOverlayParts(bd *BundleData) (base, overlay *BundleData, err error) { + base = cloneBundleData(bd) + _ = visitField(&visitorContext{ + structVisitor: clearOverlayFields, + dropNonRequiredMapKeys: false, + }, base) + + overlay = cloneBundleData(bd) + _ = visitField(&visitorContext{ + structVisitor: clearNonOverlayFields, + dropNonRequiredMapKeys: true, + }, overlay) + + return base, overlay, nil +} + +// cloneBundleData uses the gob package to perform a deep copy of bd. +func cloneBundleData(bd *BundleData) *BundleData { + return deepcopy.Copy(bd).(*BundleData) +} + +// VerifyNoOverlayFieldsPresent scans the contents of bd and returns an error +// if the bundle contains any overlay-specific values. +func VerifyNoOverlayFieldsPresent(bd *BundleData) error { + var ( + errList []error + pathStack []string + ) + + ctx := &visitorContext{ + structVisitor: func(ctx *visitorContext, val reflect.Value, typ reflect.Type) (foundOverlay bool) { + for i := 0; i < typ.NumField(); i++ { + structField := typ.Field(i) + + // Skip non-exportable and empty fields + v := val.Field(i) + if !v.CanInterface() || isZero(v) { + continue + } + + if isOverlayField(structField) { + errList = append( + errList, + fmt.Errorf( + "%s.%s can only appear in an overlay section", + strings.Join(pathStack, "."), + yamlName(structField), + ), + ) + foundOverlay = true + } + + pathStack = append(pathStack, yamlName(structField)) + if visitField(ctx, v.Interface()) { + foundOverlay = true + } + pathStack = pathStack[:len(pathStack)-1] + } + return foundOverlay + }, + indexedElemPreVisitor: func(index interface{}) { + pathStack = append(pathStack, fmt.Sprint(index)) + }, + indexedElemPostVisitor: func(_ interface{}) { + pathStack = pathStack[:len(pathStack)-1] + }, + } + + _ = visitField(ctx, bd) + if len(errList) == 0 { + return nil + } + + return &VerificationError{errList} +} + +func yamlName(structField reflect.StructField) string { + fields := strings.Split(structField.Tag.Get("yaml"), ",") + if len(fields) == 0 || fields[0] == "" { + return strings.ToLower(structField.Name) + } + + return fields[0] +} + +type visitorContext struct { + structVisitor func(ctx *visitorContext, val reflect.Value, typ reflect.Type) bool + + // An optional pre/post visitor for indexable items (slices, maps) + indexedElemPreVisitor func(index interface{}) + indexedElemPostVisitor func(index interface{}) + + dropNonRequiredMapKeys bool +} + +// visitField invokes ctx.structVisitor(val) if v is a struct and returns back +// the visitor's result. On the other hand, if val is a slice or a map, +// visitField invoke specialized functions that support iterating such types. +func visitField(ctx *visitorContext, val interface{}) bool { + if val == nil { + return false + } + typ := reflect.TypeOf(val) + v := reflect.ValueOf(val) + + // De-reference pointers + if v.Kind() == reflect.Ptr { + v = v.Elem() + if v.Kind() == reflect.Invalid { + return false + } + typ = v.Type() + } + + switch typ.Kind() { + case reflect.Struct: + return ctx.structVisitor(ctx, v, typ) + case reflect.Map: + return visitFieldsInMap(ctx, v) + case reflect.Slice: + return visitFieldsInSlice(ctx, v) + } + + // v is not a struct or something we can iterate to reach a struct + return false +} + +// visitFieldsInMap iterates the map specified by val and recursively visits +// each map element. The returned value is the logical OR of the responses +// returned by visiting all map elements. +func visitFieldsInMap(ctx *visitorContext, val reflect.Value) (result bool) { + for _, key := range val.MapKeys() { + v := val.MapIndex(key) + if !v.CanInterface() { + continue + } + + if ctx.indexedElemPreVisitor != nil { + ctx.indexedElemPreVisitor(key) + } + + visRes := visitField(ctx, v.Interface()) + result = visRes || result + + // If the map value is a non-scalar value and the visitor + // returned false (don't retain), consult the dropNonRequiredMapKeys + // hint to decide whether we need to delete the key from the map. + // + // This is required when splitting bundles into base/overlay + // bits as empty map values would be encoded as empty objects + // that the overlay merge code would mis-interpret as deletions. + if !visRes && isNonScalar(v) && ctx.dropNonRequiredMapKeys { + val.SetMapIndex(key, reflect.Value{}) + } + + if ctx.indexedElemPostVisitor != nil { + ctx.indexedElemPostVisitor(key) + } + } + + return result +} + +// visitFieldsInSlice iterates the slice specified by val and recursively +// visits each element. The returned value is the logical OR of the responses +// returned by visiting all slice elements. +func visitFieldsInSlice(ctx *visitorContext, val reflect.Value) (result bool) { + for i := 0; i < val.Len(); i++ { + v := val.Index(i) + if !v.CanInterface() { + continue + } + + if ctx.indexedElemPreVisitor != nil { + ctx.indexedElemPreVisitor(i) + } + + result = visitField(ctx, v.Interface()) || result + + if ctx.indexedElemPostVisitor != nil { + ctx.indexedElemPostVisitor(i) + } + } + + return result +} + +// clearOverlayFields is an implementation of structVisitor. It recursively +// visits all fields in the val struct and sets the ones that are tagged as +// overlay-only to the zero value for their particular type. +func clearOverlayFields(ctx *visitorContext, val reflect.Value, typ reflect.Type) (retainAncestors bool) { + for i := 0; i < typ.NumField(); i++ { + structField := typ.Field(i) + + // Skip non-exportable and empty fields + v := val.Field(i) + if !v.CanInterface() || isZero(v) { + continue + } + + // No need to recurse further down; just erase the field + if isOverlayField(structField) { + v.Set(reflect.Zero(v.Type())) + continue + } + + _ = visitField(ctx, v.Interface()) + retainAncestors = true + } + return retainAncestors +} + +// clearNonOverlayFields is an implementation of structVisitor. It recursively +// visits all fields in the val struct and sets any field that does not contain +// any overlay-only descendants to the zero value for its particular type. +func clearNonOverlayFields(ctx *visitorContext, val reflect.Value, typ reflect.Type) (retainAncestors bool) { + for i := 0; i < typ.NumField(); i++ { + structField := typ.Field(i) + + // Skip non-exportable and empty fields + v := val.Field(i) + if !v.CanInterface() || isZero(v) { + continue + } + + // If this is an overlay field we need to preserve it and all + // its ancestor fields up to the root. However, we still need + // to visit its descendants in case we need to clear additional + // non-overlay fields further down the tree. + isOverlayField := isOverlayField(structField) + if isOverlayField { + retainAncestors = true + } + + target := v.Interface() + if retain := visitField(ctx, target); !isOverlayField && !retain { + v.Set(reflect.Zero(v.Type())) + continue + } + + retainAncestors = true + } + return retainAncestors +} + +// isOverlayField returns true if a struct field is tagged as overlay-only. +func isOverlayField(structField reflect.StructField) bool { + return structField.Tag.Get("source") == "overlay-only" +} + +// isZero reports whether v is the zero value for its type. It panics if the +// argument is invalid. The implementation has been copied from the upstream Go +// repo as it has not made its way to a stable Go release yet. +func isZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Invalid: + return true + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return math.Float64bits(v.Float()) == 0 + case reflect.Complex64, reflect.Complex128: + c := v.Complex() + return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 + case reflect.Array: + for i := 0; i < v.Len(); i++ { + if !isZero(v.Index(i)) { + return false + } + } + return true + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: + return v.IsNil() + case reflect.String: + return v.Len() == 0 + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if !isZero(v.Field(i)) { + return false + } + } + return true + default: + // This should never happens, but will act as a safeguard for + // later, as a default value doesn't makes sense here. + panic(fmt.Sprintf("unexpected value of type %s passed to isZero", v.Kind().String())) + } +} + +// ReadAndMergeBundleData reads N bundle data sources, composes their contents +// together and returns the result. The first bundle data source is treated as +// a base bundle while subsequent bundle data sources are treated as overlays +// which are sequentially merged onto the base bundle. +// +// Before returning the merged bundle, ReadAndMergeBundleData will also attempt +// to resolve any include directives present in the machine annotations, +// application options and annotations. +// +// When merging an overlay into a base bundle the following rules apply for the +// BundleData struct fields: +// - if an overlay specifies a bundle-level series, it overrides the base bundle +// series. +// - overlay-defined relations are appended to the base bundle relations +// - overlay-defined machines overwrite the base bundle machines. +// - if an overlay defines an application that is not present in the base bundle, +// it will get appended to the application list. +// - if an overlay defines an empty application or saas value, it will be removed +// from the base bundle together with any associated relations. For example, to +// remove an application named "mysql" the following overlay snippet can be +// provided: +// applications: +// mysql: +// +// - if an overlay defines an application that is also present in the base bundle +// the two application specs are merged together (see following rules) +// +// ApplicationSpec merge rules: +// - if the overlay defines a value for a scalar or slice field, it will overwrite +// the value from the base spec (e.g. trust, series etc). +// - if the overlay specifies a nil/empty value for a map field, then the map +// field of the base spec will be cleared. +// - if the overlay specifies a non-empty value for a map field, its key/value +// tuples are iterated and: +// - if the value is nil/zero and the value is non-scalar, it is deleted from +// the base spec. +// - otherwise, the key/value is inserted into the base spec overwriting any +// existing entries. +func ReadAndMergeBundleData(sources ...BundleDataSource) (*BundleData, error) { + var allParts []*BundleDataPart + var partSrcIndex []int + for srcIndex, src := range sources { + if src == nil { + continue + } + + for _, part := range src.Parts() { + allParts = append(allParts, part) + partSrcIndex = append(partSrcIndex, srcIndex) + } + } + + if len(allParts) == 0 { + return nil, errors.NotValidf("malformed bundle: bundle is empty") + } + + // Treat the first part as the base bundle + base := allParts[0] + if err := VerifyNoOverlayFieldsPresent(base.Data); err != nil { + return nil, errors.Trace(err) + } + + // Merge parts and resolve include directives + for index, part := range allParts { + // Resolve any re-writing of normalisation that could cause the presence + // field to be out of sync with the actual bundle representation. + resolveOverlayPresenceFields(part) + + if index != 0 { + if err := applyOverlay(base, part); err != nil { + return nil, errors.Trace(err) + } + } + + // Relative include directives are resolved using the base path + // of the datasource that yielded this part + srcIndex := partSrcIndex[index] + incResolver := sources[srcIndex].ResolveInclude + basePath := sources[srcIndex].BasePath() + for app, appData := range base.Data.Applications { + if appData == nil { + return nil, errors.Errorf("base application %q has no body", app) + } + resolvedCharm, err := resolveRelativeCharmPath(basePath, appData.Charm) + if err != nil { + return nil, errors.Annotatef(err, "resolving relative charm path %q for application %q", appData.Charm, app) + } + appData.Charm = resolvedCharm + + for k, v := range appData.Options { + newV, changed, err := resolveIncludes(incResolver, v) + if err != nil { + return nil, errors.Annotatef(err, "processing option %q for application %q", k, app) + } + if changed { + appData.Options[k] = newV + } + } + + for k, v := range appData.Annotations { + newV, changed, err := resolveIncludes(incResolver, v) + if err != nil { + return nil, errors.Annotatef(err, "processing annotation %q for application %q", k, app) + } + if changed { + appData.Annotations[k] = newV + } + } + } + + for machine, machineData := range base.Data.Machines { + if machineData == nil { + continue + } + + for k, v := range machineData.Annotations { + newV, changed, err := resolveIncludes(incResolver, v) + if err != nil { + return nil, errors.Annotatef(err, "processing annotation %q for machine %q", k, machine) + } + if changed { + machineData.Annotations[k] = newV + } + } + } + } + + return base.Data, nil +} + +// resolveOverlayPresenceFields exists because we expose an internal bundle +// representation of a type out to the consumers of the library. This means it +// becomes very difficult to know what was re-written during the normalisation +// phase, without telling downstream consumers. +// +// The following attempts to guess when a normalisation has occurred, but the +// presence field map is out of sync with the new changes. +func resolveOverlayPresenceFields(base *BundleDataPart) { + applications := base.PresenceMap.forField("applications") + if len(applications) == 0 { + return + } + for name, app := range base.Data.Applications { + if !applications.fieldPresent(name) { + continue + } + + presence := applications.forField(name) + // If the presence map contains scale, but doesn't contain num_units + // and if the app.Scale_ has been set to zero. We can then assume that a + // normalistion has occurred. + if presence.fieldPresent("scale") && !presence.fieldPresent("num_units") && app.Scale_ == 0 && app.NumUnits > 0 { + presence["num_units"] = presence["scale"] + } + } +} + +func applyOverlay(base, overlay *BundleDataPart) error { + if overlay == nil || len(overlay.PresenceMap) == 0 { + return nil + } + if !overlay.PresenceMap.fieldPresent("applications") && len(overlay.Data.Applications) > 0 { + return errors.Errorf("bundle overlay file used deprecated 'services' key, this is not valid for bundle overlay files") + } + + // Merge applications + if len(overlay.Data.Applications) != 0 { + if base.Data.Applications == nil { + base.Data.Applications = make(map[string]*ApplicationSpec, len(overlay.Data.Applications)) + } + + fpm := overlay.PresenceMap.forField("applications") + for srcAppName, srcAppSpec := range overlay.Data.Applications { + // If the overlay map points to an empty object, delete + // it from the base bundle + if isZero(reflect.ValueOf(srcAppSpec)) { + delete(base.Data.Applications, srcAppName) + base.Data.Relations = removeRelations(base.Data.Relations, srcAppName) + continue + } + + // If this is a new application just append it; otherwise + // recursively merge the two application specs. + dstAppSpec, defined := base.Data.Applications[srcAppName] + if !defined { + base.Data.Applications[srcAppName] = srcAppSpec + continue + } + + mergeStructs(dstAppSpec, srcAppSpec, fpm.forField(srcAppName)) + } + } + + // Merge SAAS blocks + if len(overlay.Data.Saas) != 0 { + if base.Data.Saas == nil { + base.Data.Saas = make(map[string]*SaasSpec, len(overlay.Data.Saas)) + } + + fpm := overlay.PresenceMap.forField("saas") + for srcSaasName, srcSaasSpec := range overlay.Data.Saas { + // If the overlay map points to an empty object, delete + // it from the base bundle + if isZero(reflect.ValueOf(srcSaasSpec)) { + delete(base.Data.Saas, srcSaasName) + base.Data.Relations = removeRelations(base.Data.Relations, srcSaasName) + continue + } + + // if this is a new saas block just append it; otherwise + // recursively merge the two saas specs. + dstSaasSpec, defined := base.Data.Saas[srcSaasName] + if !defined { + base.Data.Saas[srcSaasName] = srcSaasSpec + continue + } + + mergeStructs(dstSaasSpec, srcSaasSpec, fpm.forField(srcSaasName)) + } + } + + // If series is set in the config, it overrides the bundle. + if series := overlay.Data.Series; series != "" { + base.Data.Series = series + } + + // Append any additional relations. + base.Data.Relations = append(base.Data.Relations, overlay.Data.Relations...) + + // Override machine definitions. + if machines := overlay.Data.Machines; machines != nil { + base.Data.Machines = machines + } + + return nil +} + +// removeRelations removes any relation defined in data that references +// the application appName. +func removeRelations(data [][]string, appName string) [][]string { + var result [][]string + for _, relation := range data { + // Keep the dud relation in the set, it will be caught by the bundle + // verify code. + if len(relation) == 2 { + left, right := relation[0], relation[1] + if left == appName || strings.HasPrefix(left, appName+":") || + right == appName || strings.HasPrefix(right, appName+":") { + continue + } + } + result = append(result, relation) + } + return result +} + +// mergeStructs iterates the fields of srcStruct and merges them into the +// equivalent fields of dstStruct using the following rules: +// +// - if src defines a value for a scalar or slice field, it will overwrite +// the value from the dst (e.g. trust, series etc). +// - if the src specifies a nil/empty value for a map field, then the map +// field of dst will be cleared. +// - if the src specifies a non-empty value for a map field, its key/value +// tuples are iterated and: +// - if the value is nil/zero and non-scalar, it is deleted from the dst map. +// - otherwise, the key/value is inserted into the dst map overwriting any +// existing entries. +func mergeStructs(dstStruct, srcStruct interface{}, fpm FieldPresenceMap) { + dst := reflect.ValueOf(dstStruct) + src := reflect.ValueOf(srcStruct) + typ := src.Type() + + // Dereference pointers + if src.Kind() == reflect.Ptr { + src = src.Elem() + typ = src.Type() + } + if dst.Kind() == reflect.Ptr { + dst = dst.Elem() + } + dstTyp := dst.Type() + + // Sanity check + if typ.Kind() != reflect.Struct || typ != dstTyp { + panic(errors.Errorf("BUG: source/destination type mismatch; expected destination to be a %q; got %q", typ.Name(), dstTyp.Name())) + } + + for i := 0; i < typ.NumField(); i++ { + // Skip non-exportable fields + structField := typ.Field(i) + srcVal := src.Field(i) + if !srcVal.CanInterface() { + continue + } + + fieldName := yamlName(structField) + if !fpm.fieldPresent(fieldName) { + continue + } + + switch srcVal.Kind() { + case reflect.Map: + // If a nil/empty map is provided then clear the destination map. + if isZero(srcVal) { + dst.Field(i).Set(reflect.MakeMap(srcVal.Type())) + continue + } + + dstMap := dst.Field(i) + if dstMap.IsNil() { + dstMap.Set(reflect.MakeMap(srcVal.Type())) + } + for _, srcKey := range srcVal.MapKeys() { + // If the key points to an empty non-scalar value delete it from the dst map + srcMapVal := srcVal.MapIndex(srcKey) + if isZero(srcMapVal) && isNonScalar(srcMapVal) { + // Setting an empty value effectively deletes the key from the map + dstMap.SetMapIndex(srcKey, reflect.Value{}) + continue + } + + dstMap.SetMapIndex(srcKey, srcMapVal) + } + case reflect.Slice: + dst.Field(i).Set(srcVal) + default: + dst.Field(i).Set(srcVal) + } + } +} + +// isNonScalar returns true if val is a non-scalar value such as a pointer, +// struct, map or slice. +func isNonScalar(val reflect.Value) bool { + kind := val.Kind() + + if kind == reflect.Interface { + kind = reflect.TypeOf(val).Kind() + } + + switch kind { + case reflect.Ptr, reflect.Struct, + reflect.Map, reflect.Slice, reflect.Array: + return true + default: + return false + } +} + +// resolveIncludes operates on v which is expected to be string. It checks the +// value for the presence of an include directive. If such a directive is +// located, resolveIncludes invokes the provided includeResolver and returns +// back its output after applying the appropriate encoding for the directive. +func resolveIncludes(includeResolver func(path string) ([]byte, error), v interface{}) (string, bool, error) { + directives := []struct { + directive string + encoder func([]byte) string + }{ + { + directive: "include-file://", + encoder: func(d []byte) string { + return string(d) + }, + }, + { + directive: "include-base64://", + encoder: base64.StdEncoding.EncodeToString, + }, + } + + val, isString := v.(string) + if !isString { + return "", false, nil + } + + for _, dir := range directives { + if !strings.HasPrefix(val, dir.directive) { + continue + } + + path := val[len(dir.directive):] + data, err := includeResolver(path) + if err != nil { + return "", false, errors.Annotatef(err, "resolving include %q", path) + } + + return dir.encoder(data), true, nil + } + + return val, false, nil +} + +// resolveRelativeCharmPath resolves charmURL into an absolute path relative +// to basePath if charmURL contains a relative path. Otherwise, the function +// returns back the original charmURL. +// +// Note: this function will only resolve paths. It will not check whether the +// referenced charm path actually exists. That is the job of the bundle +// validator. +func resolveRelativeCharmPath(basePath, charmURL string) (string, error) { + // We don't need to do anything for non-relative paths. + if !strings.HasPrefix(charmURL, ".") { + return charmURL, nil + } + + return filepath.Abs(filepath.Join(basePath, charmURL)) +} diff --git a/internal/charm/overlay_internal_test.go b/internal/charm/overlay_internal_test.go new file mode 100644 index 00000000000..082bf262aae --- /dev/null +++ b/internal/charm/overlay_internal_test.go @@ -0,0 +1,64 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" +) + +type removeRelationsSuite struct{} + +var ( + _ = gc.Suite(&removeRelationsSuite{}) + + sampleRelations = [][]string{ + {"kubernetes-master:kube-control", "kubernetes-worker:kube-control"}, + {"kubernetes-master:etcd", "etcd:db"}, + {"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"}, + {"flannel", "etcd"}, // removed :endpoint + {"flannel:cni", "kubernetes-master:cni"}, + {"flannel:cni", "kubernetes-worker:cni"}, + } +) + +func (*removeRelationsSuite) TestNil(c *gc.C) { + result := removeRelations(nil, "foo") + c.Assert(result, gc.HasLen, 0) +} + +func (*removeRelationsSuite) TestEmpty(c *gc.C) { + result := removeRelations([][]string{}, "foo") + c.Assert(result, gc.HasLen, 0) +} + +func (*removeRelationsSuite) TestAppNotThere(c *gc.C) { + result := removeRelations(sampleRelations, "foo") + c.Assert(result, jc.DeepEquals, sampleRelations) +} + +func (*removeRelationsSuite) TestAppBadRelationsKept(c *gc.C) { + badRelations := [][]string{{"single value"}, {"three", "string", "values"}} + result := removeRelations(badRelations, "foo") + c.Assert(result, jc.DeepEquals, badRelations) +} + +func (*removeRelationsSuite) TestRemoveFromRight(c *gc.C) { + result := removeRelations(sampleRelations, "etcd") + c.Assert(result, jc.DeepEquals, [][]string{ + {"kubernetes-master:kube-control", "kubernetes-worker:kube-control"}, + {"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"}, + {"flannel:cni", "kubernetes-master:cni"}, + {"flannel:cni", "kubernetes-worker:cni"}, + }) +} + +func (*removeRelationsSuite) TestRemoveFromLeft(c *gc.C) { + result := removeRelations(sampleRelations, "flannel") + c.Assert(result, jc.DeepEquals, [][]string{ + {"kubernetes-master:kube-control", "kubernetes-worker:kube-control"}, + {"kubernetes-master:etcd", "etcd:db"}, + {"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"}, + }) +} diff --git a/internal/charm/overlay_test.go b/internal/charm/overlay_test.go new file mode 100644 index 00000000000..b5c944c41e1 --- /dev/null +++ b/internal/charm/overlay_test.go @@ -0,0 +1,1013 @@ +// Copyright 2019 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "os" + "path/filepath" + "sort" + "strings" + + "github.com/juju/errors" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/internal/charm" +) + +type bundleDataOverlaySuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&bundleDataOverlaySuite{}) + +func (*bundleDataOverlaySuite) TestEmptyBaseApplication(c *gc.C) { + data := ` +applications: + apache2: +--- +series: trusty +applications: + apache2: + charm: cs:apache2-42 + series: bionic +`[1:] + + _, err := charm.ReadAndMergeBundleData(mustCreateStringDataSource(c, data)) + c.Assert(err, gc.ErrorMatches, `base application "apache2" has no body`) +} + +func (*bundleDataOverlaySuite) TestExtractBaseAndOverlayParts(c *gc.C) { + data := ` +applications: + apache2: + charm: apache2 + exposed-endpoints: + www: + expose-to-spaces: + - dmz + expose-to-cidrs: + - 13.37.0.0/16 + offers: + my-offer: + endpoints: + - apache-website + - website-cache + acl: + admin: admin + foo: consume + my-other-offer: + endpoints: + - apache-website +saas: + apache2: + url: production:admin/info.apache +series: bionic +`[1:] + + expBase := ` +applications: + apache2: + charm: apache2 +saas: + apache2: + url: production:admin/info.apache +series: bionic +`[1:] + + expOverlay := ` +applications: + apache2: + exposed-endpoints: + www: + expose-to-spaces: + - dmz + expose-to-cidrs: + - 13.37.0.0/16 + offers: + my-offer: + endpoints: + - apache-website + - website-cache + acl: + admin: admin + foo: consume + my-other-offer: + endpoints: + - apache-website +`[1:] + + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + + base, overlay, err := charm.ExtractBaseAndOverlayParts(bd) + c.Assert(err, jc.ErrorIsNil) + + baseYaml, err := yaml.Marshal(base) + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(baseYaml), gc.Equals, expBase) + + overlayYaml, err := yaml.Marshal(overlay) + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(overlayYaml), gc.Equals, expOverlay) +} + +func (*bundleDataOverlaySuite) TestExtractBaseAndOverlayPartsWithNoOverlayFields(c *gc.C) { + data := ` +bundle: kubernetes +applications: + mysql: + charm: cs:mysql + scale: 1 + wordpress: + charm: cs:wordpress + scale: 2 +relations: +- - wordpress:db + - mysql:mysql +`[1:] + + expBase := ` +bundle: kubernetes +applications: + mysql: + charm: cs:mysql + num_units: 1 + wordpress: + charm: cs:wordpress + num_units: 2 +relations: +- - wordpress:db + - mysql:mysql +`[1:] + + expOverlay := ` +{} +`[1:] + + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + + base, overlay, err := charm.ExtractBaseAndOverlayParts(bd) + c.Assert(err, jc.ErrorIsNil) + + baseYaml, err := yaml.Marshal(base) + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(baseYaml), gc.Equals, expBase) + + overlayYaml, err := yaml.Marshal(overlay) + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(overlayYaml), gc.Equals, expOverlay) +} + +func (*bundleDataOverlaySuite) TestExtractAndMergeWithMixedOverlayBits(c *gc.C) { + // In this example, mysql defines an offer whereas wordpress does not. + // + // When the visitor code examines the application map, it should report + // back that the filtered "mysql" application key should be retained + // but the "wordpress" application key should NOT be retained. The + // applications map should be retained because at least one of its keys + // has to be retained. However, the "wordpress" entry must be removed. + // If not, it would be encoded as an empty object which the overlay + // merge code would mis-interpret as a request to delete the "wordpress" + // application from the base bundle! + data := ` +bundle: kubernetes +applications: + mysql: + charm: cs:mysql + scale: 1 + offers: + my-offer: + endpoints: + - apache-website + - website-cache + acl: + admin: admin + foo: consume + wordpress: + charm: cs:wordpress + channel: edge + scale: 2 + options: + foo: bar +relations: +- - wordpress:db + - mysql:mysql +`[1:] + + expBase := ` +bundle: kubernetes +applications: + mysql: + charm: cs:mysql + num_units: 1 + wordpress: + charm: cs:wordpress + channel: edge + num_units: 2 + options: + foo: bar +relations: +- - wordpress:db + - mysql:mysql +`[1:] + + expOverlay := ` +applications: + mysql: + offers: + my-offer: + endpoints: + - apache-website + - website-cache + acl: + admin: admin + foo: consume +`[1:] + + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + + base, overlay, err := charm.ExtractBaseAndOverlayParts(bd) + c.Assert(err, jc.ErrorIsNil) + + baseYaml, err := yaml.Marshal(base) + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(baseYaml), gc.Equals, expBase) + + overlayYaml, err := yaml.Marshal(overlay) + c.Assert(err, jc.ErrorIsNil) + c.Assert(string(overlayYaml), gc.Equals, expOverlay) + + // Check that merging the output back into a bundle yields the original + r := strings.NewReader(string(baseYaml) + "\n---\n" + string(overlayYaml)) + ds, err := charm.StreamBundleDataSource(r, "") + c.Assert(err, jc.ErrorIsNil) + + newBd, err := charm.ReadAndMergeBundleData(ds) + c.Assert(err, jc.ErrorIsNil) + c.Assert(newBd, gc.DeepEquals, bd) +} + +func (*bundleDataOverlaySuite) TestVerifyNoOverlayFieldsPresent(c *gc.C) { + data := ` +applications: + apache2: + charm: apache2 + offers: + my-offer: + endpoints: + - apache-website + - website-cache + acl: + admin: admin + foo: consume + my-other-offer: + endpoints: + - apache-website +saas: + apache2: + url: production:admin/info.apache +series: bionic +` + + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + + static, overlay, err := charm.ExtractBaseAndOverlayParts(bd) + c.Assert(err, jc.ErrorIsNil) + + c.Assert(charm.VerifyNoOverlayFieldsPresent(static), gc.Equals, nil) + + expErrors := []string{ + "applications.apache2.offers can only appear in an overlay section", + "applications.apache2.offers.my-offer.endpoints can only appear in an overlay section", + "applications.apache2.offers.my-offer.acl can only appear in an overlay section", + "applications.apache2.offers.my-other-offer.endpoints can only appear in an overlay section", + } + err = charm.VerifyNoOverlayFieldsPresent(overlay) + c.Assert(err, gc.FitsTypeOf, (*charm.VerificationError)(nil)) + errors := err.(*charm.VerificationError).Errors + errStrings := make([]string, len(errors)) + for i, err := range errors { + errStrings[i] = err.Error() + } + sort.Strings(errStrings) + sort.Strings(expErrors) + c.Assert(errStrings, jc.DeepEquals, expErrors) +} + +func (*bundleDataOverlaySuite) TestVerifyNoOverlayFieldsPresentOnNilOptionValue(c *gc.C) { + data := ` +# ssl_ca is left uninitialized so it resolves to nil +ssl_ca: &ssl_ca + +applications: + apache2: + options: + foo: bar + ssl_ca: *ssl_ca +series: bionic +` + + bd, err := charm.ReadBundleData(strings.NewReader(data)) + c.Assert(err, gc.IsNil) + + static, _, err := charm.ExtractBaseAndOverlayParts(bd) + c.Assert(err, jc.ErrorIsNil) + + c.Assert(charm.VerifyNoOverlayFieldsPresent(static), gc.Equals, nil) +} + +func (*bundleDataOverlaySuite) TestOverrideCharmAndSeries(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + num_units: 1 +--- +series: trusty +applications: + apache2: + charm: cs:apache2-42 + series: bionic +`, ` +applications: + apache2: + charm: cs:apache2-42 + series: bionic + num_units: 1 +series: trusty +`, + ) +} + +func (*bundleDataOverlaySuite) TestOverrideScale(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + scale: 1 +--- +applications: + apache2: + scale: 2 +`, ` +applications: + apache2: + charm: apache2 + num_units: 2 +`, + ) +} + +func (*bundleDataOverlaySuite) TestOverrideScaleWithNumUnits(c *gc.C) { + // This shouldn't be allowed, but the code does, so we should test it! + // Notice that scale doesn't exist. + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + scale: 1 +--- +applications: + apache2: + num_units: 2 +`, ` +applications: + apache2: + charm: apache2 + num_units: 2 +`, + ) +} + +func (*bundleDataOverlaySuite) TestMultipleOverrideScale(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + scale: 1 +--- +applications: + apache2: + scale: 50 +--- +applications: + apache2: + scale: 3 +`, ` +applications: + apache2: + charm: apache2 + num_units: 3 +`, + ) +} + +func (*bundleDataOverlaySuite) TestOverrideScaleWithZero(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + scale: 1 +--- +applications: + apache2: + scale: 0 +`, ` +applications: + apache2: + charm: apache2 + num_units: 1 +`, + ) +} + +func (*bundleDataOverlaySuite) TestAddAndOverrideResourcesStorageDevicesAndBindings(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + resources: + res1: foo + storage: + dsk0: dsk0 + devices: + dev0: dev0 +--- +applications: + apache2: + resources: + res1: bar + res2: new + storage: + dsk0: vol0 + dsk1: new + devices: + dev0: net + dev1: new + bindings: + bnd0: new +`, ` +applications: + apache2: + charm: apache2 + resources: + res1: bar + res2: new + storage: + dsk0: vol0 + dsk1: new + devices: + dev0: net + dev1: new + bindings: + bnd0: new +`, + ) +} + +func (*bundleDataOverlaySuite) TestAddAndOverrideOptionsAndAnnotations(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + options: + opt1: foo + opt1: bar + mapOpt: + foo: bar +--- +applications: + apache2: + options: + opt1: foo + opt2: "" + mapOpt: + annotations: + ann1: new +`, ` +applications: + apache2: + charm: apache2 + options: + opt1: foo + opt2: "" + annotations: + ann1: new +`, + ) +} + +func (*bundleDataOverlaySuite) TestOverrideUnitsTrustConstraintsAndExposeFlags(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 +--- +applications: + apache2: + num_units: 4 + to: + - lxd/0 + - lxd/1 + - lxd/2 + - lxd/3 + expose: true +`, ` +applications: + apache2: + charm: apache2 + num_units: 4 + to: + - lxd/0 + - lxd/1 + - lxd/2 + - lxd/3 + expose: true +`, + ) +} + +func (*bundleDataOverlaySuite) TestAddModifyAndRemoveApplicationsAndRelations(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + wordpress: + charm: wordpress + dummy: + charm: dummy +relations: +- - wordpress:www + - apache2:www +--- +applications: + apache2: + charm: apache2 + wordpress: +relations: +- - dummy:www + - apache2:www +`, ` +applications: + apache2: + charm: apache2 + dummy: + charm: dummy +relations: +- - dummy:www + - apache2:www +`, + ) +} + +func (*bundleDataOverlaySuite) TestAddModifyAndRemoveSaasBlocksAndRelations(c *gc.C) { + testBundleMergeResult(c, ` +saas: + postgres: + url: jaas:admin/default.postgres +applications: + wordpress: + charm: wordpress +relations: +- - wordpress:db + - postgres:db +--- +saas: + postgres: + cockroachdb: + url: jaas:admin/default.cockroachdb +`, ` +applications: + wordpress: + charm: wordpress +saas: + cockroachdb: + url: jaas:admin/default.cockroachdb +`, + ) +} + +func (*bundleDataOverlaySuite) TestAddAndRemoveOffers(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + channel: stable + revision: 26 +--- # offer blocks are overlay-specific +applications: + apache2: + offers: + my-offer: + endpoints: + - apache-website + - website-cache + acl: + admin: admin + foo: consume + my-other-offer: + endpoints: + - apache-website +--- +applications: + apache2: + offers: + my-other-offer: +`, ` +applications: + apache2: + charm: apache2 + channel: stable + revision: 26 + offers: + my-offer: + endpoints: + - apache-website + - website-cache + acl: + admin: admin + foo: consume +`, + ) +} + +func (*bundleDataOverlaySuite) TestAddAndRemoveMachines(c *gc.C) { + testBundleMergeResult(c, ` +applications: + apache2: + charm: apache2 + channel: stable + revision: 26 +machines: + "0": {} + "1": {} +--- +machines: + "2": {} +`, ` +applications: + apache2: + charm: apache2 + channel: stable + revision: 26 +machines: + "2": {} +`, + ) +} + +func (*bundleDataOverlaySuite) TestYAMLInterpolation(c *gc.C) { + base := ` +applications: + django: + expose: true + charm: django + num_units: 1 + options: + general: good + annotations: + key1: value1 + key2: value2 + to: [1] + memcached: + charm: mem + revision: 47 + series: trusty + num_units: 1 + options: + key: value +relations: + - - "django" + - "memcached" +machines: + 1: + annotations: {foo: bar}` + + removeDjango := ` +applications: + django: +` + + addWiki := ` +defaultwiki: &DEFAULTWIKI + charm: "mediawiki" + revision: 5 + series: trusty + num_units: 1 + options: &WIKIOPTS + debug: false + name: Please set name of wiki + skin: vector + +applications: + wiki: + <<: *DEFAULTWIKI + options: + <<: *WIKIOPTS + name: The name override +relations: + - - "wiki" + - "memcached" +` + + bd, err := charm.ReadAndMergeBundleData( + mustCreateStringDataSource(c, base), + mustCreateStringDataSource(c, removeDjango), + mustCreateStringDataSource(c, addWiki), + ) + c.Assert(err, gc.IsNil) + + merged, err := yaml.Marshal(bd) + c.Assert(err, gc.IsNil) + + exp := ` +applications: + memcached: + charm: mem + revision: 47 + series: trusty + num_units: 1 + options: + key: value + wiki: + charm: mediawiki + revision: 5 + series: trusty + num_units: 1 + options: + debug: false + name: The name override + skin: vector +machines: + "1": + annotations: + foo: bar +relations: +- - wiki + - memcached +` + + c.Assert("\n"+string(merged), gc.Equals, exp) +} + +func (*bundleDataOverlaySuite) TestReadAndMergeBundleDataWithIncludes(c *gc.C) { + data := ` +applications: + apache2: + options: + opt-raw: include-file://foo + opt-b64: include-base64://foo + opt-other: + some: value + annotations: + anno-raw: include-file://foo + anno-b64: include-base64://foo + anno-other: value +machines: + "0": {} + "1": + annotations: + anno-raw: include-file://foo + anno-b64: include-base64://foo + anno-other: value +` + + ds := srcWithFakeIncludeResolver{ + src: mustCreateStringDataSource(c, data), + resolveMap: map[string][]byte{ + "foo": []byte("lorem$ipsum$"), + }, + } + + bd, err := charm.ReadAndMergeBundleData(ds) + c.Assert(err, gc.IsNil) + + merged, err := yaml.Marshal(bd) + c.Assert(err, gc.IsNil) + + exp := ` +applications: + apache2: + options: + opt-b64: bG9yZW0kaXBzdW0k + opt-other: + some: value + opt-raw: lorem$ipsum$ + annotations: + anno-b64: bG9yZW0kaXBzdW0k + anno-other: value + anno-raw: lorem$ipsum$ +machines: + "0": {} + "1": + annotations: + anno-b64: bG9yZW0kaXBzdW0k + anno-other: value + anno-raw: lorem$ipsum$ +` + + c.Assert("\n"+string(merged), gc.Equals, exp) +} + +func (*bundleDataOverlaySuite) TestBundleDataSourceRelativeIncludes(c *gc.C) { + base := ` +applications: + django: + charm: cs:django + options: + opt1: include-file://relative-to-base.txt +` + + overlays := ` +applications: + django: + charm: cs:django + options: + opt2: include-file://relative-to-overlay.txt +--- +applications: + django: + charm: cs:django + options: + opt3: include-file://relative-to-overlay.txt +` + + baseDir := c.MkDir() + mustWriteFile(c, filepath.Join(baseDir, "bundle.yaml"), base) + mustWriteFile(c, filepath.Join(baseDir, "relative-to-base.txt"), "lorem ipsum") + + ovlDir := c.MkDir() + mustWriteFile(c, filepath.Join(ovlDir, "overlays.yaml"), overlays) + mustWriteFile(c, filepath.Join(ovlDir, "relative-to-overlay.txt"), "dolor") + + bd, err := charm.ReadAndMergeBundleData( + mustCreateLocalDataSource(c, filepath.Join(baseDir, "bundle.yaml")), + mustCreateLocalDataSource(c, filepath.Join(ovlDir, "overlays.yaml")), + ) + c.Assert(err, gc.IsNil) + + merged, err := yaml.Marshal(bd) + c.Assert(err, gc.IsNil) + + exp := ` +applications: + django: + charm: cs:django + options: + opt1: lorem ipsum + opt2: dolor + opt3: dolor +` + + c.Assert("\n"+string(merged), gc.Equals, exp) +} + +func (*bundleDataOverlaySuite) TestBundleDataSourceWithEmptyOverlay(c *gc.C) { + base := ` +applications: + django: + charm: cs:django +` + + overlays := ` +--- +` + + baseDir := c.MkDir() + mustWriteFile(c, filepath.Join(baseDir, "bundle.yaml"), base) + + ovlDir := c.MkDir() + mustWriteFile(c, filepath.Join(ovlDir, "overlays.yaml"), overlays) + + bd, err := charm.ReadAndMergeBundleData( + mustCreateLocalDataSource(c, filepath.Join(baseDir, "bundle.yaml")), + mustCreateLocalDataSource(c, filepath.Join(ovlDir, "overlays.yaml")), + ) + c.Assert(err, gc.IsNil) + + merged, err := yaml.Marshal(bd) + c.Assert(err, gc.IsNil) + + exp := ` +applications: + django: + charm: cs:django +` + + c.Assert("\n"+string(merged), gc.Equals, exp) +} + +func (*bundleDataOverlaySuite) TestReadAndMergeBundleDataWithRelativeCharmPaths(c *gc.C) { + base := ` +applications: + apache2: + charm: ./apache + mysql: + charm: cs:mysql + varnish: + charm: /some/absolute/path +` + + overlay := ` +applications: + wordpress: + charm: ./wordpress +` + bd, err := charm.ReadAndMergeBundleData( + mustCreateStringDataSourceWithBasePath(c, base, "/tmp/base"), + mustCreateStringDataSourceWithBasePath(c, overlay, "/overlay"), + ) + c.Assert(err, gc.IsNil) + + merged, err := yaml.Marshal(bd) + c.Assert(err, gc.IsNil) + + exp := ` +applications: + apache2: + charm: /tmp/base/apache + mysql: + charm: cs:mysql + varnish: + charm: /some/absolute/path + wordpress: + charm: /overlay/wordpress +`[1:] + + c.Assert(string(merged), gc.Equals, exp) +} + +type srcWithFakeIncludeResolver struct { + src charm.BundleDataSource + resolveMap map[string][]byte +} + +func (s srcWithFakeIncludeResolver) Parts() []*charm.BundleDataPart { + return s.src.Parts() +} + +func (s srcWithFakeIncludeResolver) BundleBytes() []byte { + return s.src.BundleBytes() +} + +func (s srcWithFakeIncludeResolver) BasePath() string { + return s.src.BasePath() +} + +func (s srcWithFakeIncludeResolver) ResolveInclude(path string) ([]byte, error) { + var ( + data []byte + found bool + ) + + if s.resolveMap != nil { + data, found = s.resolveMap[path] + } + + if !found { + return nil, errors.NotFoundf(path) + } + + return data, nil +} + +// testBundleMergeResult reads and merges the bundle and any overlays in src, +// serializes the merged bundle back to yaml and compares it with exp. +func testBundleMergeResult(c *gc.C, src, exp string) { + bd, err := charm.ReadAndMergeBundleData(mustCreateStringDataSource(c, src)) + c.Assert(err, gc.IsNil) + + merged, err := yaml.Marshal(bd) + c.Assert(err, gc.IsNil) + c.Assert("\n"+string(merged), gc.Equals, exp) +} + +func mustWriteFile(c *gc.C, path, content string) { + err := os.WriteFile(path, []byte(content), os.ModePerm) + c.Assert(err, gc.IsNil) +} + +func mustCreateLocalDataSource(c *gc.C, path string) charm.BundleDataSource { + ds, err := charm.LocalBundleDataSource(path) + c.Assert(err, gc.IsNil, gc.Commentf(path)) + return ds +} + +func mustCreateStringDataSource(c *gc.C, data string) charm.BundleDataSource { + ds, err := charm.StreamBundleDataSource(strings.NewReader(data), "") + c.Assert(err, gc.IsNil) + return ds +} + +func mustCreateStringDataSourceWithBasePath(c *gc.C, data, basePath string) charm.BundleDataSource { + ds, err := charm.StreamBundleDataSource(strings.NewReader(data), basePath) + c.Assert(err, gc.IsNil) + return ds +} diff --git a/internal/charm/package_test.go b/internal/charm/package_test.go new file mode 100644 index 00000000000..0cefafedfb6 --- /dev/null +++ b/internal/charm/package_test.go @@ -0,0 +1,14 @@ +// Copyright 2021 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + stdtesting "testing" + + gc "gopkg.in/check.v1" +) + +func Test(t *stdtesting.T) { + gc.TestingT(t) +} diff --git a/internal/charm/payloads.go b/internal/charm/payloads.go new file mode 100644 index 00000000000..91f18e22326 --- /dev/null +++ b/internal/charm/payloads.go @@ -0,0 +1,73 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + + "github.com/juju/names/v5" + "github.com/juju/schema" +) + +var payloadClassSchema = schema.FieldMap( + schema.Fields{ + "type": schema.String(), + }, + schema.Defaults{}, +) + +// PayloadClass holds the information about a payload class, as stored +// in a charm's metadata. +type PayloadClass struct { + // Name identifies the payload class. + Name string + + // Type identifies the type of payload (e.g. kvm, docker). + Type string +} + +func parsePayloadClasses(data interface{}) map[string]PayloadClass { + if data == nil { + return nil + } + + result := make(map[string]PayloadClass) + for name, val := range data.(map[string]interface{}) { + result[name] = parsePayloadClass(name, val) + } + + return result +} + +func parsePayloadClass(name string, data interface{}) PayloadClass { + payloadClass := PayloadClass{ + Name: name, + } + if data == nil { + return payloadClass + } + pcMap := data.(map[string]interface{}) + + if val := pcMap["type"]; val != nil { + payloadClass.Type = val.(string) + } + + return payloadClass +} + +// Validate checks the payload class to ensure its data is valid. +func (pc PayloadClass) Validate() error { + if pc.Name == "" { + return fmt.Errorf("payload class missing name") + } + if !names.IsValidPayload(pc.Name) { + return fmt.Errorf("invalid payload class %q", pc.Name) + } + + if pc.Type == "" { + return fmt.Errorf("payload class missing type") + } + + return nil +} diff --git a/internal/charm/payloads_test.go b/internal/charm/payloads_test.go new file mode 100644 index 00000000000..9d9f82f453c --- /dev/null +++ b/internal/charm/payloads_test.go @@ -0,0 +1,96 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +var _ = gc.Suite(&payloadClassSuite{}) + +type payloadClassSuite struct{} + +func (s *payloadClassSuite) TestParsePayloadClassOkay(c *gc.C) { + name := "my-payload" + data := map[string]interface{}{ + "type": "docker", + } + payloadClass := charm.ParsePayloadClass(name, data) + + c.Check(payloadClass, jc.DeepEquals, charm.PayloadClass{ + Name: "my-payload", + Type: "docker", + }) +} + +func (s *payloadClassSuite) TestParsePayloadClassMissingName(c *gc.C) { + name := "" + data := map[string]interface{}{ + "type": "docker", + } + payloadClass := charm.ParsePayloadClass(name, data) + + c.Check(payloadClass, jc.DeepEquals, charm.PayloadClass{ + Name: "", + Type: "docker", + }) +} + +func (s *payloadClassSuite) TestParsePayloadClassEmpty(c *gc.C) { + name := "my-payload" + var data map[string]interface{} + payloadClass := charm.ParsePayloadClass(name, data) + + c.Check(payloadClass, jc.DeepEquals, charm.PayloadClass{ + Name: "my-payload", + }) +} + +func (s *payloadClassSuite) TestValidateFull(c *gc.C) { + payloadClass := charm.PayloadClass{ + Name: "my-payload", + Type: "docker", + } + err := payloadClass.Validate() + + c.Check(err, jc.ErrorIsNil) +} + +func (s *payloadClassSuite) TestValidateZeroValue(c *gc.C) { + var payloadClass charm.PayloadClass + err := payloadClass.Validate() + + c.Check(err, gc.NotNil) +} + +func (s *payloadClassSuite) TestValidateMissingName(c *gc.C) { + payloadClass := charm.PayloadClass{ + Type: "docker", + } + err := payloadClass.Validate() + + c.Check(err, gc.ErrorMatches, `payload class missing name`) +} + +func (s *payloadClassSuite) TestValidateBadName(c *gc.C) { + payloadClass := charm.PayloadClass{ + Name: "my-###-payload", + Type: "docker", + } + err := payloadClass.Validate() + + c.Check(err, gc.ErrorMatches, `invalid payload class "my-###-payload"`) +} + +func (s *payloadClassSuite) TestValidateMissingType(c *gc.C) { + payloadClass := charm.PayloadClass{ + Name: "my-payload", + } + err := payloadClass.Validate() + + c.Check(err, gc.ErrorMatches, `payload class missing type`) +} diff --git a/internal/charm/repository/charmhub.go b/internal/charm/repository/charmhub.go index 336d321f245..d2cfcc1fe0e 100644 --- a/internal/charm/repository/charmhub.go +++ b/internal/charm/repository/charmhub.go @@ -9,10 +9,10 @@ import ( "net/url" "strings" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" diff --git a/internal/charm/repository/charmhub_test.go b/internal/charm/repository/charmhub_test.go index f614754ce34..4e204f277f7 100644 --- a/internal/charm/repository/charmhub_test.go +++ b/internal/charm/repository/charmhub_test.go @@ -9,10 +9,10 @@ import ( "net/url" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4/hash" diff --git a/internal/charm/repository/mocks/charmhub_client_mock.go b/internal/charm/repository/mocks/charmhub_client_mock.go index ba2cddc91ab..d8efdb3501d 100644 --- a/internal/charm/repository/mocks/charmhub_client_mock.go +++ b/internal/charm/repository/mocks/charmhub_client_mock.go @@ -14,7 +14,7 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" charmhub "github.com/juju/juju/internal/charmhub" transport "github.com/juju/juju/internal/charmhub/transport" gomock "go.uber.org/mock/gomock" diff --git a/internal/charm/resource/fingerprint.go b/internal/charm/resource/fingerprint.go new file mode 100644 index 00000000000..e7667596220 --- /dev/null +++ b/internal/charm/resource/fingerprint.go @@ -0,0 +1,66 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource + +import ( + stdhash "hash" + "io" + + "github.com/juju/errors" + "github.com/juju/utils/v4/hash" +) + +var newHash, validateSum = hash.SHA384() + +// Fingerprint represents the unique fingerprint value of a resource's data. +type Fingerprint struct { + hash.Fingerprint +} + +// NewFingerprint returns wraps the provided raw fingerprint bytes. +// This function roundtrips with Fingerprint.Bytes(). +func NewFingerprint(raw []byte) (Fingerprint, error) { + fp, err := hash.NewFingerprint(raw, validateSum) + if err != nil { + return Fingerprint{}, errors.Trace(err) + } + return Fingerprint{Fingerprint: fp}, nil +} + +// ParseFingerprint returns wraps the provided raw fingerprint string. +// This function roundtrips with Fingerprint.String(). +func ParseFingerprint(raw string) (Fingerprint, error) { + fp, err := hash.ParseHexFingerprint(raw, validateSum) + if err != nil { + return Fingerprint{}, errors.Trace(err) + } + return Fingerprint{Fingerprint: fp}, nil +} + +// GenerateFingerprint returns the fingerprint for the provided data. +func GenerateFingerprint(reader io.Reader) (Fingerprint, error) { + fp, err := hash.GenerateFingerprint(reader, newHash) + if err != nil { + return Fingerprint{}, errors.Trace(err) + } + return Fingerprint{fp}, nil +} + +// Fingerprint is a hash that may be used to generate fingerprints. +type FingerprintHash struct { + stdhash.Hash +} + +// NewFingerprintHash returns a hash that may be used to create fingerprints. +func NewFingerprintHash() *FingerprintHash { + return &FingerprintHash{ + Hash: newHash(), + } +} + +// Fingerprint returns the current fingerprint of the hash. +func (fph FingerprintHash) Fingerprint() Fingerprint { + fp := hash.NewValidFingerprint(fph) + return Fingerprint{Fingerprint: fp} +} diff --git a/internal/charm/resource/fingerprint_test.go b/internal/charm/resource/fingerprint_test.go new file mode 100644 index 00000000000..f8621f86003 --- /dev/null +++ b/internal/charm/resource/fingerprint_test.go @@ -0,0 +1,143 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource_test + +import ( + "crypto/sha512" + "encoding/hex" + "strings" + + "github.com/juju/errors" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm/resource" +) + +func newFingerprint(c *gc.C, data string) ([]byte, string) { + hash := sha512.New384() + _, err := hash.Write([]byte(data)) + c.Assert(err, jc.ErrorIsNil) + raw := hash.Sum(nil) + + hexStr := hex.EncodeToString(raw) + return raw, hexStr +} + +var _ = gc.Suite(&FingerprintSuite{}) + +type FingerprintSuite struct{} + +func (s *FingerprintSuite) TestNewFingerprintOkay(c *gc.C) { + expected, _ := newFingerprint(c, "spamspamspam") + + fp, err := resource.NewFingerprint(expected) + c.Assert(err, jc.ErrorIsNil) + raw := fp.Bytes() + + c.Check(raw, jc.DeepEquals, expected) +} + +func (s *FingerprintSuite) TestNewFingerprintTooSmall(c *gc.C) { + expected, _ := newFingerprint(c, "spamspamspam") + + _, err := resource.NewFingerprint(expected[:10]) + + c.Check(err, jc.ErrorIs, errors.NotValid) + c.Check(err, gc.ErrorMatches, `.*too small.*`) +} + +func (s *FingerprintSuite) TestNewFingerprintTooBig(c *gc.C) { + expected, _ := newFingerprint(c, "spamspamspam") + + _, err := resource.NewFingerprint(append(expected, 1, 2, 3)) + + c.Check(err, jc.ErrorIs, errors.NotValid) + c.Check(err, gc.ErrorMatches, `.*too big.*`) +} + +func (s *FingerprintSuite) TestParseFingerprintOkay(c *gc.C) { + _, expected := newFingerprint(c, "spamspamspam") + + fp, err := resource.ParseFingerprint(expected) + c.Assert(err, jc.ErrorIsNil) + hex := fp.String() + + c.Check(hex, jc.DeepEquals, expected) +} + +func (s *FingerprintSuite) TestParseFingerprintNonHex(c *gc.C) { + _, err := resource.ParseFingerprint("abc") // not hex + + c.Check(err, gc.ErrorMatches, `.*odd length hex string.*`) +} + +func (s *FingerprintSuite) TestGenerateFingerprint(c *gc.C) { + expected, _ := newFingerprint(c, "spamspamspam") + data := strings.NewReader("spamspamspam") + + fp, err := resource.GenerateFingerprint(data) + c.Assert(err, jc.ErrorIsNil) + raw := fp.Bytes() + + c.Check(raw, jc.DeepEquals, expected) +} + +func (s *FingerprintSuite) TestString(c *gc.C) { + raw, expected := newFingerprint(c, "spamspamspam") + fp, err := resource.NewFingerprint(raw) + c.Assert(err, jc.ErrorIsNil) + + hex := fp.String() + + c.Check(hex, gc.Equals, expected) +} + +func (s *FingerprintSuite) TestRoundtripString(c *gc.C) { + _, expected := newFingerprint(c, "spamspamspam") + + fp, err := resource.ParseFingerprint(expected) + c.Assert(err, jc.ErrorIsNil) + hex := fp.String() + + c.Check(hex, gc.Equals, expected) +} + +func (s *FingerprintSuite) TestBytes(c *gc.C) { + expected, _ := newFingerprint(c, "spamspamspam") + fp, err := resource.NewFingerprint(expected) + c.Assert(err, jc.ErrorIsNil) + + raw := fp.Bytes() + + c.Check(raw, jc.DeepEquals, expected) +} + +func (s *FingerprintSuite) TestRoundtripBytes(c *gc.C) { + expected, _ := newFingerprint(c, "spamspamspam") + + fp, err := resource.NewFingerprint(expected) + c.Assert(err, jc.ErrorIsNil) + raw := fp.Bytes() + + c.Check(raw, jc.DeepEquals, expected) +} + +func (s *FingerprintSuite) TestValidateOkay(c *gc.C) { + raw, _ := newFingerprint(c, "spamspamspam") + fp, err := resource.NewFingerprint(raw) + c.Assert(err, jc.ErrorIsNil) + + err = fp.Validate() + + c.Check(err, jc.ErrorIsNil) +} + +func (s *FingerprintSuite) TestValidateZero(c *gc.C) { + var fp resource.Fingerprint + err := fp.Validate() + + c.Check(err, jc.ErrorIs, errors.NotValid) + c.Check(err, gc.ErrorMatches, `zero-value fingerprint not valid`) +} diff --git a/internal/charm/resource/meta.go b/internal/charm/resource/meta.go new file mode 100644 index 00000000000..b9c2e5377a0 --- /dev/null +++ b/internal/charm/resource/meta.go @@ -0,0 +1,65 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource + +import ( + "fmt" + "strings" + + "github.com/juju/errors" +) + +// Meta holds the information about a resource, as stored +// in a charm's metadata. +type Meta struct { + // Name identifies the resource. + Name string + + // Type identifies the type of resource (e.g. "file"). + Type Type + + // TODO(ericsnow) Rename Path to Filename? + + // Path is the relative path of the file or directory where the + // resource will be stored under the unit's data directory. The path + // is resolved against a subdirectory assigned to the resource. For + // example, given an application named "spam", a resource "eggs", and a + // path "eggs.tgz", the fully resolved storage path for the resource + // would be: + // /var/lib/juju/agent/spam-0/resources/eggs/eggs.tgz + Path string + + // Description holds optional user-facing info for the resource. + Description string +} + +// Validate checks the resource metadata to ensure the data is valid. +func (meta Meta) Validate() error { + if meta.Name == "" { + return errors.NewNotValid(nil, "resource missing name") + } + + var typeUnknown Type + if meta.Type == typeUnknown { + return errors.NewNotValid(nil, "resource missing type") + } + if err := meta.Type.Validate(); err != nil { + msg := fmt.Sprintf("invalid resource type %v: %v", meta.Type, err) + return errors.NewNotValid(nil, msg) + } + + if meta.Type == TypeFile && meta.Path == "" { + // TODO(ericsnow) change "filename" to "path" + return errors.NewNotValid(nil, "resource missing filename") + } + if meta.Type == TypeFile { + if strings.Contains(meta.Path, "/") { + msg := fmt.Sprintf(`filename cannot contain "/" (got %q)`, meta.Path) + return errors.NewNotValid(nil, msg) + } + // TODO(ericsnow) Constrain Path to alphanumeric? + } + + return nil +} diff --git a/internal/charm/resource/meta_test.go b/internal/charm/resource/meta_test.go new file mode 100644 index 00000000000..620520f882b --- /dev/null +++ b/internal/charm/resource/meta_test.go @@ -0,0 +1,118 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource_test + +import ( + "github.com/juju/errors" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm/resource" +) + +var _ = gc.Suite(&MetaSuite{}) + +type MetaSuite struct{} + +func (s *MetaSuite) TestValidateFull(c *gc.C) { + res := resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + } + err := res.Validate() + + c.Check(err, jc.ErrorIsNil) +} + +func (s *MetaSuite) TestValidateZeroValue(c *gc.C) { + var res resource.Meta + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) +} + +func (s *MetaSuite) TestValidateMissingName(c *gc.C) { + res := resource.Meta{ + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + } + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `resource missing name`) +} + +func (s *MetaSuite) TestValidateMissingType(c *gc.C) { + res := resource.Meta{ + Name: "my-resource", + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + } + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `resource missing type`) +} + +func (s *MetaSuite) TestValidateMissingPath(c *gc.C) { + res := resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Description: "One line that is useful when operators need to push it.", + } + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `resource missing filename`) +} + +func (s *MetaSuite) TestValidateNestedPath(c *gc.C) { + res := resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "spam/eggs", + } + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `.*filename cannot contain "/" .*`) +} + +func (s *MetaSuite) TestValidateAbsolutePath(c *gc.C) { + res := resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "/spam/eggs", + } + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `.*filename cannot contain "/" .*`) +} + +func (s *MetaSuite) TestValidateSuspectPath(c *gc.C) { + res := resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "git@github.com:juju/juju.git", + } + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `.*filename cannot contain "/" .*`) +} + +func (s *MetaSuite) TestValidateMissingComment(c *gc.C) { + res := resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + } + err := res.Validate() + + c.Check(err, jc.ErrorIsNil) +} diff --git a/internal/charm/resource/origin.go b/internal/charm/resource/origin.go new file mode 100644 index 00000000000..fec35e58fa0 --- /dev/null +++ b/internal/charm/resource/origin.go @@ -0,0 +1,50 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package resource + +import ( + "github.com/juju/errors" +) + +// These are the valid resource origins. +const ( + originUnknown Origin = iota + OriginUpload + OriginStore +) + +var origins = map[Origin]string{ + OriginUpload: "upload", + OriginStore: "store", +} + +// Origin identifies where a charm's resource comes from. +type Origin int + +// ParseOrigin converts the provided string into an Origin. +// If it is not a known origin then an error is returned. +func ParseOrigin(value string) (Origin, error) { + for o, str := range origins { + if value == str { + return o, nil + } + } + return originUnknown, errors.Errorf("unknown origin %q", value) +} + +// String returns the printable representation of the origin. +func (o Origin) String() string { + return origins[o] +} + +// Validate ensures that the origin is correct. +func (o Origin) Validate() error { + // Ideally, only the (unavoidable) zero value would be invalid. + // However, typedef'ing int means that the use of int literals + // could result in invalid Type values other than the zero value. + if _, ok := origins[o]; !ok { + return errors.NewNotValid(nil, "unknown origin") + } + return nil +} diff --git a/internal/charm/resource/origin_test.go b/internal/charm/resource/origin_test.go new file mode 100644 index 00000000000..7c41eaf8ef3 --- /dev/null +++ b/internal/charm/resource/origin_test.go @@ -0,0 +1,58 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package resource_test + +import ( + "github.com/juju/errors" + "github.com/juju/testing" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm/resource" +) + +type OriginSuite struct { + testing.IsolationSuite +} + +var _ = gc.Suite(&OriginSuite{}) + +func (OriginSuite) TestParseOriginKnown(c *gc.C) { + recognized := map[string]resource.Origin{ + "upload": resource.OriginUpload, + "store": resource.OriginStore, + } + for value, expected := range recognized { + origin, err := resource.ParseOrigin(value) + + c.Check(err, jc.ErrorIsNil) + c.Check(origin, gc.Equals, expected) + } +} + +func (OriginSuite) TestParseOriginUnknown(c *gc.C) { + _, err := resource.ParseOrigin("") + + c.Check(err, gc.ErrorMatches, `.*unknown origin "".*`) +} + +func (OriginSuite) TestValidateKnown(c *gc.C) { + recognized := []resource.Origin{ + resource.OriginUpload, + resource.OriginStore, + } + for _, origin := range recognized { + err := origin.Validate() + + c.Check(err, jc.ErrorIsNil) + } +} + +func (OriginSuite) TestValidateUnknown(c *gc.C) { + var origin resource.Origin + err := origin.Validate() + + c.Check(errors.Cause(err), jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `.*unknown origin.*`) +} diff --git a/internal/charm/resource/package_test.go b/internal/charm/resource/package_test.go new file mode 100644 index 00000000000..6fa43a0e24d --- /dev/null +++ b/internal/charm/resource/package_test.go @@ -0,0 +1,14 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource_test + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func Test(t *testing.T) { + gc.TestingT(t) +} diff --git a/internal/charm/resource/resource.go b/internal/charm/resource/resource.go new file mode 100644 index 00000000000..339cb14f1ca --- /dev/null +++ b/internal/charm/resource/resource.go @@ -0,0 +1,94 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource + +import ( + "fmt" + + "github.com/juju/errors" +) + +// Resource describes a charm's resource in the charm store. +type Resource struct { + Meta + + // Origin identifies where the resource will come from. + Origin Origin + + // Revision is the charm store revision of the resource. + Revision int + + // Fingerprint is the SHA-384 checksum for the resource blob. + Fingerprint Fingerprint + + // Size is the size of the resource, in bytes. + Size int64 +} + +// Validate checks the payload class to ensure its data is valid. +func (res Resource) Validate() error { + if err := res.Meta.Validate(); err != nil { + return errors.Annotate(err, "bad metadata") + } + + if err := res.Origin.Validate(); err != nil { + return errors.Annotate(err, "bad origin") + } + + if err := res.validateRevision(); err != nil { + return errors.Annotate(err, "bad revision") + } + + if res.Type == TypeFile { + if err := res.validateFileInfo(); err != nil { + return errors.Annotate(err, "bad file info") + } + } + + return nil +} + +func (res Resource) validateRevision() error { + if res.Origin == OriginUpload { + // We do not care about the revision, so we don't check it. + // TODO(ericsnow) Ensure Revision is 0 for OriginUpload? + return nil + } + + if res.Revision < 0 && res.isFileAvailable() { + return errors.NewNotValid(nil, fmt.Sprintf("must be non-negative, got %d", res.Revision)) + } + + return nil +} + +func (res Resource) validateFileInfo() error { + if res.Fingerprint.IsZero() { + if res.Size > 0 { + return errors.NewNotValid(nil, "missing fingerprint") + } + } else { + if err := res.Fingerprint.Validate(); err != nil { + return errors.Annotate(err, "bad fingerprint") + } + } + + if res.Size < 0 { + return errors.NewNotValid(nil, "negative size") + } + + return nil +} + +// isFileAvailable determines whether or not the resource info indicates +// that the resource file is available. +func (res Resource) isFileAvailable() bool { + if !res.Fingerprint.IsZero() { + return true + } + if res.Size > 0 { + return true + } + return false +} diff --git a/internal/charm/resource/resource_test.go b/internal/charm/resource/resource_test.go new file mode 100644 index 00000000000..b42dffe9839 --- /dev/null +++ b/internal/charm/resource/resource_test.go @@ -0,0 +1,219 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource_test + +import ( + "github.com/juju/errors" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm/resource" +) + +var fingerprint = []byte("123456789012345678901234567890123456789012345678") + +var _ = gc.Suite(&ResourceSuite{}) + +type ResourceSuite struct{} + +func (s *ResourceSuite) TestValidateFull(c *gc.C) { + fp, err := resource.NewFingerprint(fingerprint) + c.Assert(err, jc.ErrorIsNil) + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + Origin: resource.OriginStore, + Revision: 1, + Fingerprint: fp, + Size: 1, + } + err = res.Validate() + + c.Check(err, jc.ErrorIsNil) +} + +func (s *ResourceSuite) TestValidateZeroValue(c *gc.C) { + var res resource.Resource + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) +} + +func (s *ResourceSuite) TestValidateBadMetadata(c *gc.C) { + var meta resource.Meta + c.Assert(meta.Validate(), gc.NotNil) + + fp, err := resource.NewFingerprint(fingerprint) + c.Assert(err, jc.ErrorIsNil) + res := resource.Resource{ + Meta: meta, + Origin: resource.OriginStore, + Revision: 1, + Fingerprint: fp, + } + err = res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `bad metadata: .*`) +} + +func (s *ResourceSuite) TestValidateBadOrigin(c *gc.C) { + var origin resource.Origin + c.Assert(origin.Validate(), gc.NotNil) + fp, err := resource.NewFingerprint(fingerprint) + c.Assert(err, jc.ErrorIsNil) + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + Origin: origin, + Revision: 1, + Fingerprint: fp, + } + err = res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `bad origin: .*`) +} + +func (s *ResourceSuite) TestValidateUploadNegativeRevision(c *gc.C) { + fp, err := resource.NewFingerprint(fingerprint) + c.Assert(err, jc.ErrorIsNil) + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + Origin: resource.OriginUpload, + Revision: -1, + Fingerprint: fp, + Size: 10, + } + err = res.Validate() + + c.Check(err, jc.ErrorIsNil) +} + +func (s *ResourceSuite) TestValidateStoreNegativeRevisionNoFile(c *gc.C) { + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + Origin: resource.OriginStore, + Revision: -1, + } + err := res.Validate() + + c.Check(err, jc.ErrorIsNil) +} + +func (s *ResourceSuite) TestValidateBadRevision(c *gc.C) { + fp, err := resource.NewFingerprint(fingerprint) + c.Assert(err, jc.ErrorIsNil) + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + Origin: resource.OriginStore, + Revision: -1, + Fingerprint: fp, + } + err = res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `bad revision: must be non-negative, got -1`) +} + +func (s *ResourceSuite) TestValidateZeroValueFingerprint(c *gc.C) { + var fp resource.Fingerprint + c.Assert(fp.Validate(), gc.NotNil) + + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + Origin: resource.OriginStore, + Revision: 1, + Fingerprint: fp, + } + err := res.Validate() + + c.Check(err, jc.ErrorIsNil) +} + +func (s *ResourceSuite) TestValidateMissingFingerprint(c *gc.C) { + var fp resource.Fingerprint + c.Assert(fp.Validate(), gc.NotNil) + + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + Origin: resource.OriginStore, + Revision: 1, + Fingerprint: fp, + Size: 10, + } + err := res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `bad file info: missing fingerprint`) +} + +func (s *ResourceSuite) TestValidateDockerType(c *gc.C) { + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeContainerImage, + Description: "One line that is useful when operators need to push it.", + }, + Origin: resource.OriginStore, + Revision: 1, + } + err := res.Validate() + + c.Check(err, jc.ErrorIsNil) +} + +func (s *ResourceSuite) TestValidateBadSize(c *gc.C) { + fp, err := resource.NewFingerprint(fingerprint) + c.Assert(err, jc.ErrorIsNil) + res := resource.Resource{ + Meta: resource.Meta{ + Name: "my-resource", + Type: resource.TypeFile, + Path: "filename.tgz", + Description: "One line that is useful when operators need to push it.", + }, + Origin: resource.OriginStore, + Revision: 1, + Fingerprint: fp, + Size: -1, + } + err = res.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `bad file info: negative size`) +} diff --git a/internal/charm/resource/sort.go b/internal/charm/resource/sort.go new file mode 100644 index 00000000000..14f61ecb1d4 --- /dev/null +++ b/internal/charm/resource/sort.go @@ -0,0 +1,19 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource + +import ( + "sort" +) + +// Sort sorts the provided resources. +func Sort(resources []Resource) { + sort.Sort(byName(resources)) +} + +type byName []Resource + +func (sorted byName) Len() int { return len(sorted) } +func (sorted byName) Swap(i, j int) { sorted[i], sorted[j] = sorted[j], sorted[i] } +func (sorted byName) Less(i, j int) bool { return sorted[i].Name < sorted[j].Name } diff --git a/internal/charm/resource/type.go b/internal/charm/resource/type.go new file mode 100644 index 00000000000..4867347194d --- /dev/null +++ b/internal/charm/resource/type.go @@ -0,0 +1,50 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource + +import ( + "github.com/juju/errors" +) + +// These are the valid resource types (except for unknown). +const ( + typeUnknown Type = iota + TypeFile + TypeContainerImage +) + +var types = map[Type]string{ + TypeFile: "file", + TypeContainerImage: "oci-image", +} + +// Type enumerates the recognized resource types. +type Type int + +// ParseType converts a string to a Type. If the given value does not +// match a recognized type then an error is returned. +func ParseType(value string) (Type, error) { + for rt, str := range types { + if value == str { + return rt, nil + } + } + return typeUnknown, errors.Errorf("unsupported resource type %q", value) +} + +// String returns the printable representation of the type. +func (rt Type) String() string { + return types[rt] +} + +// Validate ensures that the type is valid. +func (rt Type) Validate() error { + // Ideally, only the (unavoidable) zero value would be invalid. + // However, typedef'ing int means that the use of int literals + // could result in invalid Type values other than the zero value. + if _, ok := types[rt]; !ok { + return errors.NewNotValid(nil, "unknown resource type") + } + return nil +} diff --git a/internal/charm/resource/type_test.go b/internal/charm/resource/type_test.go new file mode 100644 index 00000000000..1395acfd744 --- /dev/null +++ b/internal/charm/resource/type_test.go @@ -0,0 +1,83 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package resource_test + +import ( + "github.com/juju/errors" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm/resource" +) + +var _ = gc.Suite(&TypeSuite{}) + +type TypeSuite struct{} + +func (s *TypeSuite) TestParseTypeOkay(c *gc.C) { + for resourceType, expected := range map[string]resource.Type{ + "file": resource.TypeFile, + "oci-image": resource.TypeContainerImage, + } { + rt, err := resource.ParseType(resourceType) + c.Assert(err, jc.ErrorIsNil) + + c.Check(rt, gc.Equals, expected) + } +} + +func (s *TypeSuite) TestParseTypeEmpty(c *gc.C) { + rt, err := resource.ParseType("") + + c.Check(err, gc.ErrorMatches, `unsupported resource type ""`) + var unknown resource.Type + c.Check(rt, gc.Equals, unknown) +} + +func (s *TypeSuite) TestParseTypeUnsupported(c *gc.C) { + rt, err := resource.ParseType("spam") + + c.Check(err, gc.ErrorMatches, `unsupported resource type "spam"`) + var unknown resource.Type + c.Check(rt, gc.Equals, unknown) +} + +func (s *TypeSuite) TestTypeStringSupported(c *gc.C) { + supported := map[resource.Type]string{ + resource.TypeFile: "file", + resource.TypeContainerImage: "oci-image", + } + for rt, expected := range supported { + str := rt.String() + + c.Check(str, gc.Equals, expected) + } +} + +func (s *TypeSuite) TestTypeStringUnknown(c *gc.C) { + var unknown resource.Type + str := unknown.String() + + c.Check(str, gc.Equals, "") +} + +func (s *TypeSuite) TestTypeValidateSupported(c *gc.C) { + supported := []resource.Type{ + resource.TypeFile, + resource.TypeContainerImage, + } + for _, rt := range supported { + err := rt.Validate() + + c.Check(err, jc.ErrorIsNil) + } +} + +func (s *TypeSuite) TestTypeValidateUnknown(c *gc.C) { + var unknown resource.Type + err := unknown.Validate() + + c.Check(err, jc.Satisfies, errors.IsNotValid) + c.Check(err, gc.ErrorMatches, `unknown resource type`) +} diff --git a/internal/charm/resources.go b/internal/charm/resources.go new file mode 100644 index 00000000000..565985b3e27 --- /dev/null +++ b/internal/charm/resources.go @@ -0,0 +1,86 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "fmt" + + "github.com/juju/errors" + "github.com/juju/schema" + + "github.com/juju/juju/internal/charm/resource" +) + +var resourceSchema = schema.FieldMap( + schema.Fields{ + "type": schema.String(), + "filename": schema.String(), // TODO(ericsnow) Change to "path"? + "description": schema.String(), + }, + schema.Defaults{ + "type": resource.TypeFile.String(), + "filename": "", + "description": "", + }, +) + +func parseMetaResources(data interface{}) (map[string]resource.Meta, error) { + if data == nil { + return nil, nil + } + + result := make(map[string]resource.Meta) + for name, val := range data.(map[string]interface{}) { + meta, err := parseResourceMeta(name, val) + if err != nil { + return nil, err + } + result[name] = meta + } + + return result, nil +} + +func validateMetaResources(resources map[string]resource.Meta) error { + for name, res := range resources { + if res.Name != name { + return fmt.Errorf("mismatch on resource name (%q != %q)", res.Name, name) + } + if err := res.Validate(); err != nil { + return err + } + } + return nil +} + +// parseResourceMeta parses the provided data into a Meta, assuming +// that the data has first been checked with resourceSchema. +func parseResourceMeta(name string, data interface{}) (resource.Meta, error) { + meta := resource.Meta{ + Name: name, + } + + if data == nil { + return meta, nil + } + rMap := data.(map[string]interface{}) + + if val := rMap["type"]; val != nil { + var err error + meta.Type, err = resource.ParseType(val.(string)) + if err != nil { + return meta, errors.Trace(err) + } + } + + if val := rMap["filename"]; val != nil { + meta.Path = val.(string) + } + + if val := rMap["description"]; val != nil { + meta.Description = val.(string) + } + + return meta, nil +} diff --git a/internal/charm/resources_test.go b/internal/charm/resources_test.go new file mode 100644 index 00000000000..9944ae8388a --- /dev/null +++ b/internal/charm/resources_test.go @@ -0,0 +1,77 @@ +// Copyright 2015 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +var _ = gc.Suite(&resourceSuite{}) + +type resourceSuite struct{} + +func (s *resourceSuite) TestSchemaOkay(c *gc.C) { + raw := map[interface{}]interface{}{ + "type": "file", + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + } + v, err := charm.ResourceSchema.Coerce(raw, nil) + c.Assert(err, jc.ErrorIsNil) + + c.Check(v, jc.DeepEquals, map[string]interface{}{ + "type": "file", + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + }) +} + +func (s *resourceSuite) TestSchemaMissingType(c *gc.C) { + raw := map[interface{}]interface{}{ + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + } + v, err := charm.ResourceSchema.Coerce(raw, nil) + c.Assert(err, jc.ErrorIsNil) + + c.Check(v, jc.DeepEquals, map[string]interface{}{ + "type": "file", + "filename": "filename.tgz", + "description": "One line that is useful when operators need to push it.", + }) +} + +func (s *resourceSuite) TestSchemaUnknownType(c *gc.C) { + raw := map[interface{}]interface{}{ + "type": "repo", + "filename": "juju", + "description": "One line that is useful when operators need to push it.", + } + v, err := charm.ResourceSchema.Coerce(raw, nil) + c.Assert(err, jc.ErrorIsNil) + + c.Check(v, jc.DeepEquals, map[string]interface{}{ + "type": "repo", + "filename": "juju", + "description": "One line that is useful when operators need to push it.", + }) +} + +func (s *resourceSuite) TestSchemaMissingComment(c *gc.C) { + raw := map[interface{}]interface{}{ + "type": "file", + "filename": "filename.tgz", + } + v, err := charm.ResourceSchema.Coerce(raw, nil) + c.Assert(err, jc.ErrorIsNil) + + c.Check(v, jc.DeepEquals, map[string]interface{}{ + "type": "file", + "filename": "filename.tgz", + "description": "", + }) +} diff --git a/internal/charm/url.go b/internal/charm/url.go new file mode 100644 index 00000000000..a2fecb1b769 --- /dev/null +++ b/internal/charm/url.go @@ -0,0 +1,477 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "encoding/json" + "fmt" + gourl "net/url" + "regexp" + "strconv" + "strings" + + "github.com/juju/errors" + "github.com/juju/mgo/v3/bson" + "github.com/juju/utils/v4/arch" +) + +// Schema represents the different types of valid schemas. +type Schema string + +const ( + // Local represents a local charm URL, describes as a file system path. + Local Schema = "local" + + // CharmHub schema represents the charmhub charm repository. + CharmHub Schema = "ch" +) + +// Prefix creates a url with the given prefix, useful for typed schemas. +func (s Schema) Prefix(url string) string { + return fmt.Sprintf("%s:%s", s, url) +} + +// Matches attempts to compare if a schema string matches the schema. +func (s Schema) Matches(other string) bool { + return string(s) == other +} + +func (s Schema) String() string { + return string(s) +} + +// Location represents a charm location, which must declare a path component +// and a string representation. +type Location interface { + Path() string + String() string +} + +// URL represents a charm or bundle location: +// +// local:oneiric/wordpress +// ch:wordpress +// ch:amd64/jammy/wordpress-30 +type URL struct { + Schema string // "ch" or "local". + Name string // "wordpress". + Revision int // -1 if unset, N otherwise. + Series string // "precise" or "" if unset; "bundle" if it's a bundle. + Architecture string // "amd64" or "" if unset for charmstore (v1) URLs. +} + +var ( + validArch = regexp.MustCompile("^[a-z]+([a-z0-9]+)?$") + validSeries = regexp.MustCompile("^[a-z]+([a-z0-9]+)?$") + validName = regexp.MustCompile("^[a-z][a-z0-9]*(-[a-z0-9]*[a-z][a-z0-9]*)*$") +) + +// ValidateSchema returns an error if the schema is invalid. +// +// Valid schemas for the URL are: +// - ch: charm hub +// - local: local file + +func ValidateSchema(schema string) error { + switch schema { + case CharmHub.String(), Local.String(): + return nil + } + return errors.NotValidf("schema %q", schema) +} + +// IsValidSeries reports whether series is a valid series in charm or bundle +// URLs. +func IsValidSeries(series string) bool { + return validSeries.MatchString(series) +} + +// ValidateSeries returns an error if the given series is invalid. +func ValidateSeries(series string) error { + if IsValidSeries(series) { + return nil + } + return errors.NotValidf("series name %q", series) +} + +// IsValidArchitecture reports whether the architecture is a valid architecture +// in charm or bundle URLs. +func IsValidArchitecture(architecture string) bool { + return validArch.MatchString(architecture) && arch.IsSupportedArch(architecture) +} + +// ValidateArchitecture returns an error if the given architecture is invalid. +func ValidateArchitecture(arch string) error { + if IsValidArchitecture(arch) { + return nil + } + return errors.NotValidf("architecture name %q", arch) +} + +// IsValidName reports whether name is a valid charm or bundle name. +func IsValidName(name string) bool { + return validName.MatchString(name) +} + +// ValidateName returns an error if the given name is invalid. +func ValidateName(name string) error { + if IsValidName(name) { + return nil + } + return errors.NotValidf("name %q", name) +} + +// WithRevision returns a URL equivalent to url but with Revision set +// to revision. +func (u *URL) WithRevision(revision int) *URL { + urlCopy := *u + urlCopy.Revision = revision + return &urlCopy +} + +// WithArchitecture returns a URL equivalent to url but with Architecture set +// to architecture. +func (u *URL) WithArchitecture(arch string) *URL { + urlCopy := *u + urlCopy.Architecture = arch + return &urlCopy +} + +// WithSeries returns a URL equivalent to url but with Series set +// to series. +func (u *URL) WithSeries(series string) *URL { + urlCopy := *u + urlCopy.Series = series + return &urlCopy +} + +// MustParseURL works like ParseURL, but panics in case of errors. +func MustParseURL(url string) *URL { + u, err := ParseURL(url) + if err != nil { + panic(err) + } + return u +} + +// ParseURL parses the provided charm URL string into its respective +// structure. +// +// A missing schema is assumed to be 'ch'. +func ParseURL(url string) (*URL, error) { + u, err := gourl.Parse(url) + if err != nil { + return nil, errors.Errorf("cannot parse charm or bundle URL: %q", url) + } + if u.RawQuery != "" || u.Fragment != "" || u.User != nil { + return nil, errors.Errorf("charm or bundle URL %q has unrecognized parts", url) + } + var curl *URL + switch { + case CharmHub.Matches(u.Scheme): + // Handle talking to the new style of the schema. + curl, err = parseCharmhubURL(u) + case u.Opaque != "": + u.Path = u.Opaque + curl, err = parseLocalURL(u, url) + default: + // Handle the fact that anything without a prefix is now a CharmHub + // charm URL. + curl, err = parseCharmhubURL(u) + } + if err != nil { + return nil, errors.Trace(err) + } + if curl.Schema == "" { + return nil, errors.Errorf("expected schema for charm or bundle URL: %q", url) + } + return curl, nil +} + +func parseLocalURL(url *gourl.URL, originalURL string) (*URL, error) { + if !Local.Matches(url.Scheme) { + return nil, errors.NotValidf("cannot parse URL %q: schema %q", url, url.Scheme) + } + r := URL{Schema: Local.String()} + + parts := strings.Split(url.Path[0:], "/") + if len(parts) < 1 || len(parts) > 4 { + return nil, errors.Errorf("charm or bundle URL has invalid form: %q", originalURL) + } + + // ~ + if strings.HasPrefix(parts[0], "~") { + return nil, errors.Errorf("local charm or bundle URL with user name: %q", originalURL) + } + + if len(parts) > 2 { + return nil, errors.Errorf("charm or bundle URL has invalid form: %q", originalURL) + } + + // + if len(parts) == 2 { + r.Series, parts = parts[0], parts[1:] + if err := ValidateSeries(r.Series); err != nil { + return nil, errors.Annotatef(err, "cannot parse URL %q", originalURL) + } + } + if len(parts) < 1 { + return nil, errors.Errorf("URL without charm or bundle name: %q", originalURL) + } + + // [-] + r.Name, r.Revision = extractRevision(parts[0]) + if err := ValidateName(r.Name); err != nil { + return nil, errors.Annotatef(err, "cannot parse URL %q", url) + } + return &r, nil +} + +func (u *URL) path() string { + var parts []string + if u.Architecture != "" { + parts = append(parts, u.Architecture) + } + if u.Series != "" { + parts = append(parts, u.Series) + } + if u.Revision >= 0 { + parts = append(parts, fmt.Sprintf("%s-%d", u.Name, u.Revision)) + } else { + parts = append(parts, u.Name) + } + return strings.Join(parts, "/") +} + +// FullPath returns the full path of a URL path including the schema. +func (u *URL) FullPath() string { + return fmt.Sprintf("%s:%s", u.Schema, u.Path()) +} + +// Path returns the path of the URL without the schema. +func (u *URL) Path() string { + return u.path() +} + +// String returns the string representation of the URL. +func (u *URL) String() string { + return u.FullPath() +} + +// GetBSON turns u into a bson.Getter so it can be saved directly +// on a MongoDB database with mgo. +// +// TODO (stickupkid): This should not be here, as this is purely for mongo +// data stores and that should be implemented at the site of data store, not +// dependant on the library. +func (u *URL) GetBSON() (interface{}, error) { + if u == nil { + return nil, nil + } + return u.String(), nil +} + +// SetBSON turns u into a bson.Setter so it can be loaded directly +// from a MongoDB database with mgo. +// +// TODO (stickupkid): This should not be here, as this is purely for mongo +// data stores and that should be implemented at the site of data store, not +// dependant on the library. +func (u *URL) SetBSON(raw bson.Raw) error { + if raw.Kind == 10 { + return bson.SetZero + } + var s string + err := raw.Unmarshal(&s) + if err != nil { + return err + } + url, err := ParseURL(s) + if err != nil { + return err + } + *u = *url + return nil +} + +// MarshalJSON will marshal the URL into a slice of bytes in a JSON +// representation. +func (u *URL) MarshalJSON() ([]byte, error) { + if u == nil { + panic("cannot marshal nil *charm.URL") + } + return json.Marshal(u.FullPath()) +} + +// UnmarshalJSON will unmarshal the URL from a JSON representation. +func (u *URL) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + url, err := ParseURL(s) + if err != nil { + return err + } + *u = *url + return nil +} + +// MarshalText implements encoding.TextMarshaler by +// returning u.FullPath() +func (u *URL) MarshalText() ([]byte, error) { + if u == nil { + return nil, nil + } + return []byte(u.FullPath()), nil +} + +// UnmarshalText implements encoding.TestUnmarshaler by +// parsing the data with ParseURL. +func (u *URL) UnmarshalText(data []byte) error { + url, err := ParseURL(string(data)) + if err != nil { + return err + } + *u = *url + return nil +} + +// Quote translates a charm url string into one which can be safely used +// in a file path. ASCII letters, ASCII digits, dot and dash stay the +// same; other characters are translated to their hex representation +// surrounded by underscores. +func Quote(unsafe string) string { + safe := make([]byte, 0, len(unsafe)*4) + for i := 0; i < len(unsafe); i++ { + b := unsafe[i] + switch { + case b >= 'a' && b <= 'z', + b >= 'A' && b <= 'Z', + b >= '0' && b <= '9', + b == '.', + b == '-': + safe = append(safe, b) + default: + safe = append(safe, fmt.Sprintf("_%02x_", b)...) + } + } + return string(safe) +} + +// parseCharmhubURL will attempt to parse an identifier URL. The identifier +// URL is split up into 3 parts, some of which are optional and some are +// mandatory. +// +// - architecture (optional) +// - series (optional) +// - name +// - revision (optional) +// +// Examples are as follows: +// +// - ch:amd64/foo-1 +// - ch:amd64/focal/foo-1 +// - ch:foo-1 +// - ch:foo +// - ch:amd64/focal/foo +func parseCharmhubURL(url *gourl.URL) (*URL, error) { + r := URL{ + Schema: CharmHub.String(), + Revision: -1, + } + + path := url.Path + if url.Opaque != "" { + path = url.Opaque + } + + parts := strings.Split(strings.Trim(path, "/"), "/") + if len(parts) == 0 || len(parts) > 3 { + return nil, errors.Errorf(`charm or bundle URL %q malformed`, url) + } + + // ~ + if strings.HasPrefix(parts[0], "~") { + return nil, errors.NotValidf("charmhub charm or bundle URL with user name: %q", url) + } + + var nameRev string + switch len(parts) { + case 3: + r.Architecture, r.Series, nameRev = parts[0], parts[1], parts[2] + + if err := ValidateArchitecture(r.Architecture); err != nil { + return nil, errors.Annotatef(err, "in URL %q", url) + } + case 2: + // Since both the architecture and series are optional, + // the first part can be either architecture or series. + // To differentiate between them, we go ahead and try to + // validate the first part as an architecture to decide. + + if err := ValidateArchitecture(parts[0]); err == nil { + r.Architecture, nameRev = parts[0], parts[1] + } else { + r.Series, nameRev = parts[0], parts[1] + } + + default: + nameRev = parts[0] + } + + // Mandatory + r.Name, r.Revision = extractRevision(nameRev) + if err := ValidateName(r.Name); err != nil { + return nil, errors.Annotatef(err, "cannot parse name and/or revision in URL %q", url) + } + + // Optional + if r.Series != "" { + if err := ValidateSeries(r.Series); err != nil { + return nil, errors.Annotatef(err, "in URL %q", url) + } + } + + return &r, nil +} + +// EnsureSchema will ensure that the scheme for a given URL is correct and +// valid. If the url does not specify a schema, the provided defaultSchema +// will be injected to it. +func EnsureSchema(url string, defaultSchema Schema) (string, error) { + u, err := gourl.Parse(url) + if err != nil { + return "", errors.Errorf("cannot parse charm or bundle URL: %q", url) + } + switch Schema(u.Scheme) { + case CharmHub, Local: + return url, nil + case Schema(""): + // If the schema is empty, we fall back to the default schema. + return defaultSchema.Prefix(url), nil + default: + return "", errors.NotValidf("schema %q", u.Scheme) + } +} + +func extractRevision(name string) (string, int) { + revision := -1 + for i := len(name) - 1; i > 0; i-- { + c := name[i] + if c >= '0' && c <= '9' { + continue + } + if c == '-' && i != len(name)-1 { + var err error + revision, err = strconv.Atoi(name[i+1:]) + if err != nil { + panic(err) // We just checked it was right. + } + name = name[:i] + } + break + } + return name, revision +} diff --git a/internal/charm/url_test.go b/internal/charm/url_test.go new file mode 100644 index 00000000000..69479004127 --- /dev/null +++ b/internal/charm/url_test.go @@ -0,0 +1,304 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/juju/mgo/v3/bson" + jc "github.com/juju/testing/checkers" + gc "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + + "github.com/juju/juju/internal/charm" +) + +type URLSuite struct{} + +var _ = gc.Suite(&URLSuite{}) + +var urlTests = []struct { + s, err string + exact string + url *charm.URL +}{{ + s: "local:series/name-1", + url: &charm.URL{Schema: "local", Name: "name", Revision: 1, Series: "series", Architecture: ""}, +}, { + s: "local:series/name", + url: &charm.URL{Schema: "local", Name: "name", Revision: -1, Series: "series", Architecture: ""}, +}, { + s: "local:series/n0-0n-n0", + url: &charm.URL{Schema: "local", Name: "n0-0n-n0", Revision: -1, Series: "series", Architecture: ""}, +}, { + s: "local:name", + url: &charm.URL{Schema: "local", Name: "name", Revision: -1, Series: "", Architecture: ""}, +}, { + s: "bs:~user/series/name-1", + err: `cannot parse URL $URL: schema "bs" not valid`, +}, { + s: ":foo", + err: `cannot parse charm or bundle URL: $URL`, +}, { + s: "local:~user/series/name", + err: `local charm or bundle URL with user name: $URL`, +}, { + s: "local:~user/name", + err: `local charm or bundle URL with user name: $URL`, +}, { + s: "amd64/name", + url: &charm.URL{Schema: "ch", Name: "name", Revision: -1, Series: "", Architecture: "amd64"}, + exact: "ch:amd64/name", +}, { + s: "foo", + url: &charm.URL{Schema: "ch", Name: "foo", Revision: -1, Series: "", Architecture: ""}, + exact: "ch:foo", +}, { + s: "foo-1", + exact: "ch:foo-1", + url: &charm.URL{Schema: "ch", Name: "foo", Revision: 1, Series: "", Architecture: ""}, +}, { + s: "n0-n0-n0", + exact: "ch:n0-n0-n0", + url: &charm.URL{Schema: "ch", Name: "n0-n0-n0", Revision: -1, Series: "", Architecture: ""}, +}, { + s: "local:foo", + exact: "local:foo", + url: &charm.URL{Schema: "local", Name: "foo", Revision: -1, Series: "", Architecture: ""}, +}, { + s: "arm64/series/bar", + url: &charm.URL{Schema: "ch", Name: "bar", Revision: -1, Series: "series", Architecture: "arm64"}, + exact: "ch:arm64/series/bar", +}, { + s: "ch:name", + url: &charm.URL{Schema: "ch", Name: "name", Revision: -1, Series: "", Architecture: ""}, +}, { + s: "ch:name-suffix", + url: &charm.URL{Schema: "ch", Name: "name-suffix", Revision: -1, Series: "", Architecture: ""}, +}, { + s: "ch:name-1", + url: &charm.URL{Schema: "ch", Name: "name", Revision: 1, Series: "", Architecture: ""}, +}, { + s: "ch:focal/istio-gateway-74", + url: &charm.URL{Schema: "ch", Name: "istio-gateway", Revision: 74, Series: "focal", Architecture: ""}, +}, { + s: "ch:amd64/istio-gateway-74", + url: &charm.URL{Schema: "ch", Name: "istio-gateway", Revision: 74, Series: "", Architecture: "amd64"}, +}, { + s: "ch:arm64/name", + url: &charm.URL{Schema: "ch", Name: "name", Revision: -1, Series: "", Architecture: "arm64"}, + exact: "ch:arm64/name", +}, { + s: "ch:~user/name", + err: `charmhub charm or bundle URL with user name: "ch:~user/name" not valid`, +}, { + s: "ch:purple/series/name-0", + err: `in URL "ch:purple/series/name-0": architecture name "purple" not valid`, +}, { + s: "ch:nam-!e", + err: `cannot parse name and/or revision in URL "ch:nam-!e": name "nam-!e" not valid`, +}, { + s: "cs:testme", + err: `cannot parse URL "cs:testme": schema "cs" not valid`, +}} + +func (s *URLSuite) TestParseURL(c *gc.C) { + for i, t := range urlTests { + c.Logf("test %d: %q", i, t.s) + + expectStr := t.s + if t.exact != "" { + expectStr = t.exact + } + url, uerr := charm.ParseURL(t.s) + if t.err != "" { + t.err = strings.Replace(t.err, "$URL", regexp.QuoteMeta(fmt.Sprintf("%q", t.s)), -1) + c.Check(uerr, gc.ErrorMatches, t.err) + c.Check(url, gc.IsNil) + continue + } + c.Assert(uerr, gc.IsNil) + c.Check(url, gc.DeepEquals, t.url) + c.Check(url.String(), gc.Equals, expectStr) + + // URL strings are generated as expected. Reversability is preserved + // with v1 URLs. + if t.exact != "" { + c.Check(url.String(), gc.Equals, t.exact) + } else { + c.Check(url.String(), gc.Equals, t.s) + } + } +} + +var ensureSchemaTests = []struct { + input, expected, err string +}{ + {input: "foo", expected: "ch:foo"}, + {input: "foo-1", expected: "ch:foo-1"}, + {input: "~user/foo", expected: "ch:~user/foo"}, + {input: "series/foo", expected: "ch:series/foo"}, + {input: "local:foo", expected: "local:foo"}, + { + input: "unknown:foo", + err: `schema "unknown" not valid`, + }, +} + +func (s *URLSuite) TestInferURLNoDefaultSeries(c *gc.C) { + for i, t := range ensureSchemaTests { + c.Logf("%d: %s", i, t.input) + inferred, err := charm.EnsureSchema(t.input, charm.CharmHub) + if t.err != "" { + c.Assert(err, gc.ErrorMatches, t.err) + continue + } + + c.Assert(err, gc.IsNil) + c.Assert(inferred, gc.Equals, t.expected) + } +} + +var validTests = []struct { + valid func(string) bool + string string + expect bool +}{ + + {valid: charm.IsValidName, string: "", expect: false}, + {valid: charm.IsValidName, string: "wordpress", expect: true}, + {valid: charm.IsValidName, string: "Wordpress", expect: false}, + {valid: charm.IsValidName, string: "word-press", expect: true}, + {valid: charm.IsValidName, string: "word press", expect: false}, + {valid: charm.IsValidName, string: "word^press", expect: false}, + {valid: charm.IsValidName, string: "-wordpress", expect: false}, + {valid: charm.IsValidName, string: "wordpress-", expect: false}, + {valid: charm.IsValidName, string: "wordpress2", expect: true}, + {valid: charm.IsValidName, string: "wordpress-2", expect: false}, + {valid: charm.IsValidName, string: "word2-press2", expect: true}, + + {valid: charm.IsValidSeries, string: "", expect: false}, + {valid: charm.IsValidSeries, string: "precise", expect: true}, + {valid: charm.IsValidSeries, string: "Precise", expect: false}, + {valid: charm.IsValidSeries, string: "pre cise", expect: false}, + {valid: charm.IsValidSeries, string: "pre-cise", expect: false}, + {valid: charm.IsValidSeries, string: "pre^cise", expect: false}, + {valid: charm.IsValidSeries, string: "prec1se", expect: true}, + {valid: charm.IsValidSeries, string: "-precise", expect: false}, + {valid: charm.IsValidSeries, string: "precise-", expect: false}, + {valid: charm.IsValidSeries, string: "precise-1", expect: false}, + {valid: charm.IsValidSeries, string: "precise1", expect: true}, + {valid: charm.IsValidSeries, string: "pre-c1se", expect: false}, + + {valid: charm.IsValidArchitecture, string: "amd64", expect: true}, + {valid: charm.IsValidArchitecture, string: "~amd64", expect: false}, + {valid: charm.IsValidArchitecture, string: "not-an-arch", expect: false}, +} + +func (s *URLSuite) TestValidCheckers(c *gc.C) { + for i, t := range validTests { + c.Logf("test %d: %s", i, t.string) + c.Assert(t.valid(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string)) + } +} + +func (s *URLSuite) TestMustParseURL(c *gc.C) { + url := charm.MustParseURL("ch:series/name") + c.Assert(url, gc.DeepEquals, &charm.URL{Schema: "ch", Name: "name", Revision: -1, Series: "series", Architecture: ""}) + f := func() { charm.MustParseURL("local:@@/name") } + c.Assert(f, gc.PanicMatches, "cannot parse URL \"local:@@/name\": series name \"@@\" not valid") +} + +func (s *URLSuite) TestWithRevision(c *gc.C) { + url := charm.MustParseURL("ch:series/name") + other := url.WithRevision(1) + c.Assert(url, gc.DeepEquals, &charm.URL{Schema: "ch", Name: "name", Revision: -1, Series: "series", Architecture: ""}) + c.Assert(other, gc.DeepEquals, &charm.URL{Schema: "ch", Name: "name", Revision: 1, Series: "series", Architecture: ""}) + + // Should always copy. The opposite behavior is error prone. + c.Assert(other.WithRevision(1), gc.Not(gc.Equals), other) + c.Assert(other.WithRevision(1), gc.DeepEquals, other) +} + +var codecs = []struct { + Name string + Marshal func(interface{}) ([]byte, error) + Unmarshal func([]byte, interface{}) error +}{{ + Name: "bson", + Marshal: bson.Marshal, + Unmarshal: bson.Unmarshal, +}, { + Name: "json", + Marshal: json.Marshal, + Unmarshal: json.Unmarshal, +}, { + Name: "yaml", + Marshal: yaml.Marshal, + Unmarshal: yaml.Unmarshal, +}} + +func (s *URLSuite) TestURLCodecs(c *gc.C) { + for i, codec := range codecs { + c.Logf("codec %d: %v", i, codec.Name) + type doc struct { + URL *charm.URL `json:",omitempty" bson:",omitempty" yaml:",omitempty"` + } + url := charm.MustParseURL("ch:name") + v0 := doc{URL: url} + data, err := codec.Marshal(v0) + c.Assert(err, jc.ErrorIsNil) + var v doc + err = codec.Unmarshal(data, &v) + c.Assert(err, jc.ErrorIsNil) + c.Assert(v, gc.DeepEquals, v0) + + // Check that the underlying representation + // is a string. + type strDoc struct { + URL string + } + var vs strDoc + err = codec.Unmarshal(data, &vs) + c.Assert(err, jc.ErrorIsNil) + c.Assert(vs.URL, gc.Equals, "ch:name") + + data, err = codec.Marshal(doc{}) + c.Assert(err, jc.ErrorIsNil) + v = doc{} + err = codec.Unmarshal(data, &v) + c.Assert(err, jc.ErrorIsNil) + c.Assert(v.URL, gc.IsNil, gc.Commentf("data: %q", data)) + } +} + +func (s *URLSuite) TestJSONGarbage(c *gc.C) { + // unmarshalling json gibberish + for _, value := range []string{":{", `"ch:{}+<"`, `"ch:~_~/f00^^&^/baaaar$%-?"`} { + err := json.Unmarshal([]byte(value), new(struct{ URL *charm.URL })) + c.Check(err, gc.NotNil) + } +} + +type QuoteSuite struct{} + +var _ = gc.Suite(&QuoteSuite{}) + +func (s *QuoteSuite) TestUnmodified(c *gc.C) { + // Check that a string containing only valid + // chars stays unmodified. + in := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-" + out := charm.Quote(in) + c.Assert(out, gc.Equals, in) +} + +func (s *QuoteSuite) TestQuote(c *gc.C) { + // Check that invalid chars are translated correctly. + in := "hello_there/how'are~you-today.sir" + out := charm.Quote(in) + c.Assert(out, gc.Equals, "hello_5f_there_2f_how_27_are_7e_you-today.sir") +} diff --git a/internal/charm/version.go b/internal/charm/version.go new file mode 100644 index 00000000000..7165f793098 --- /dev/null +++ b/internal/charm/version.go @@ -0,0 +1,26 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm + +import ( + "bufio" + "fmt" + "io" + "strings" + + "github.com/juju/errors" +) + +// ReadVersion extracts the VCS version from a charm's version file. +func ReadVersion(r io.Reader) (string, error) { + scanner := bufio.NewScanner(r) + scanner.Scan() + if err := scanner.Err(); err != nil { + return "", errors.Annotate(err, "cannot read version file") + } + + // bzr revision info starts with "revision-id: " so strip that. + revLine := strings.TrimPrefix(scanner.Text(), "revision-id: ") + return fmt.Sprintf("%.100s", revLine), nil +} diff --git a/internal/charm/version_test.go b/internal/charm/version_test.go new file mode 100644 index 00000000000..7ffc5885dd8 --- /dev/null +++ b/internal/charm/version_test.go @@ -0,0 +1,32 @@ +// Copyright 2011, 2012, 2013 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package charm_test + +import ( + "strings" + + gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" +) + +type VersionSuite struct{} + +var _ = gc.Suite(&VersionSuite{}) + +func (s *VersionSuite) TestReadVersion(c *gc.C) { + specs := []struct { + version string + expect string + }{ + {"7215482", "7215482"}, + {"revision-id: foo@bar.com-20131222180823-abcdefg", "foo@bar.com-20131222180823-abcdefg"}, + } + for i, t := range specs { + c.Logf("test %d", i) + v, err := charm.ReadVersion(strings.NewReader(t.version)) + c.Check(err, gc.IsNil) + c.Assert(v, gc.Equals, t.expect) + } +} diff --git a/internal/charmhub/client.go b/internal/charmhub/client.go index 5ac221dbea6..755b9fa03c7 100644 --- a/internal/charmhub/client.go +++ b/internal/charmhub/client.go @@ -24,8 +24,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" charmmetrics "github.com/juju/juju/core/charm/metrics" corelogger "github.com/juju/juju/core/logger" diff --git a/internal/charmhub/download.go b/internal/charmhub/download.go index 5e93b65516f..ca1a1ddce6a 100644 --- a/internal/charmhub/download.go +++ b/internal/charmhub/download.go @@ -11,8 +11,8 @@ import ( "os" "runtime/pprof" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" corelogger "github.com/juju/juju/core/logger" "github.com/juju/juju/core/trace" diff --git a/internal/cloudconfig/instancecfg/instancecfg.go b/internal/cloudconfig/instancecfg/instancecfg.go index 547b1ec5db0..7e03cf9d271 100644 --- a/internal/cloudconfig/instancecfg/instancecfg.go +++ b/internal/cloudconfig/instancecfg/instancecfg.go @@ -15,8 +15,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/proxy" "github.com/juju/utils/v4/shell" diff --git a/internal/cloudconfig/podcfg/image.go b/internal/cloudconfig/podcfg/image.go index 63e6f4420d3..145fc709cfa 100644 --- a/internal/cloudconfig/podcfg/image.go +++ b/internal/cloudconfig/podcfg/image.go @@ -8,8 +8,8 @@ import ( "strings" "github.com/docker/distribution/reference" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/version/v2" "github.com/juju/juju/controller" diff --git a/internal/cloudconfig/podcfg/image_test.go b/internal/cloudconfig/podcfg/image_test.go index 43e01cf0b39..9b88758ef79 100644 --- a/internal/cloudconfig/podcfg/image_test.go +++ b/internal/cloudconfig/podcfg/image_test.go @@ -4,7 +4,7 @@ package podcfg_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" gc "gopkg.in/check.v1" diff --git a/internal/cloudconfig/userdatacfg_test.go b/internal/cloudconfig/userdatacfg_test.go index cd67bbf34fd..74970e65cbf 100644 --- a/internal/cloudconfig/userdatacfg_test.go +++ b/internal/cloudconfig/userdatacfg_test.go @@ -15,8 +15,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" "github.com/juju/proxy" diff --git a/internal/cloudconfig/userdatacfg_unix.go b/internal/cloudconfig/userdatacfg_unix.go index 6620d1aecd6..0ce855be729 100644 --- a/internal/cloudconfig/userdatacfg_unix.go +++ b/internal/cloudconfig/userdatacfg_unix.go @@ -14,9 +14,9 @@ import ( "strings" "text/template" - "github.com/juju/charm/v13" "github.com/juju/errors" "github.com/juju/featureflag" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" "github.com/juju/proxy" diff --git a/internal/container/broker/lxd-broker_test.go b/internal/container/broker/lxd-broker_test.go index bc0dea8990c..58a1353f4ea 100644 --- a/internal/container/broker/lxd-broker_test.go +++ b/internal/container/broker/lxd-broker_test.go @@ -7,8 +7,8 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/migration/migration.go b/internal/migration/migration.go index 3dffa64b5cc..15a510777b6 100644 --- a/internal/migration/migration.go +++ b/internal/migration/migration.go @@ -12,9 +12,9 @@ import ( "net/url" "os" - "github.com/juju/charm/v13" "github.com/juju/description/v6" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/naturalsort" "github.com/juju/version/v2" diff --git a/internal/migration/migration_test.go b/internal/migration/migration_test.go index 78a327fcbfb..78d15129eeb 100644 --- a/internal/migration/migration_test.go +++ b/internal/migration/migration_test.go @@ -10,9 +10,9 @@ import ( "io" "net/url" - "github.com/juju/charm/v13" "github.com/juju/description/v6" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/internal/resource/charmhub.go b/internal/resource/charmhub.go index 256ba0bca35..08f9f073121 100644 --- a/internal/resource/charmhub.go +++ b/internal/resource/charmhub.go @@ -7,8 +7,8 @@ import ( "context" "net/url" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/kr/pretty" corelogger "github.com/juju/juju/core/logger" diff --git a/internal/resource/charmhub_test.go b/internal/resource/charmhub_test.go index cef76554afc..d809e96f28f 100644 --- a/internal/resource/charmhub_test.go +++ b/internal/resource/charmhub_test.go @@ -7,8 +7,8 @@ import ( "bytes" "io" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/internal/resource/export_test.go b/internal/resource/export_test.go index 3407635a736..055837a99d6 100644 --- a/internal/resource/export_test.go +++ b/internal/resource/export_test.go @@ -6,7 +6,7 @@ package resource import ( "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" corelogger "github.com/juju/juju/core/logger" diff --git a/internal/resource/interfaces.go b/internal/resource/interfaces.go index 94b592a4ddd..5a8200a9567 100644 --- a/internal/resource/interfaces.go +++ b/internal/resource/interfaces.go @@ -8,7 +8,7 @@ import ( "io" "net/url" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/core/resources" "github.com/juju/juju/internal/charmhub" diff --git a/internal/resource/mocks/cache_mock.go b/internal/resource/mocks/cache_mock.go index 9e630a67adc..e9529d04b93 100644 --- a/internal/resource/mocks/cache_mock.go +++ b/internal/resource/mocks/cache_mock.go @@ -13,7 +13,7 @@ import ( io "io" reflect "reflect" - resource "github.com/juju/charm/v13/resource" + resource "github.com/juju/juju/internal/charm/resource" resources "github.com/juju/juju/core/resources" state "github.com/juju/juju/state" gomock "go.uber.org/mock/gomock" diff --git a/internal/resource/opener.go b/internal/resource/opener.go index 0898fe43936..15966f427c5 100644 --- a/internal/resource/opener.go +++ b/internal/resource/opener.go @@ -8,9 +8,9 @@ import ( "io" "github.com/im7mortal/kmutex" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/core/objectstore" diff --git a/internal/resource/opener_test.go b/internal/resource/opener_test.go index 0700eb358cd..8e47e00eb1c 100644 --- a/internal/resource/opener_test.go +++ b/internal/resource/opener_test.go @@ -9,9 +9,9 @@ import ( "sync" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/resource/resource.go b/internal/resource/resource.go index 615b9c5e24a..1267446170a 100644 --- a/internal/resource/resource.go +++ b/internal/resource/resource.go @@ -6,7 +6,7 @@ package resource import ( "io" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" internallogger "github.com/juju/juju/internal/logger" ) diff --git a/internal/resource/retryclient.go b/internal/resource/retryclient.go index 91b0c7fce59..8ccc339df42 100644 --- a/internal/resource/retryclient.go +++ b/internal/resource/retryclient.go @@ -7,9 +7,9 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/retry" ) diff --git a/internal/worker/bootstrap/bootstrap_mock_test.go b/internal/worker/bootstrap/bootstrap_mock_test.go index c2e27e379a4..9ade6501e4c 100644 --- a/internal/worker/bootstrap/bootstrap_mock_test.go +++ b/internal/worker/bootstrap/bootstrap_mock_test.go @@ -14,7 +14,7 @@ import ( http "net/http" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" cloud "github.com/juju/juju/cloud" controller "github.com/juju/juju/controller" credential "github.com/juju/juju/core/credential" diff --git a/internal/worker/bootstrap/deployer.go b/internal/worker/bootstrap/deployer.go index 7f06b5f79f7..8d995503529 100644 --- a/internal/worker/bootstrap/deployer.go +++ b/internal/worker/bootstrap/deployer.go @@ -8,8 +8,8 @@ import ( "io" "os" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" "github.com/juju/juju/controller" diff --git a/internal/worker/bootstrap/worker_test.go b/internal/worker/bootstrap/worker_test.go index 3b19de32c82..dfda04017f8 100644 --- a/internal/worker/bootstrap/worker_test.go +++ b/internal/worker/bootstrap/worker_test.go @@ -9,7 +9,7 @@ import ( "path/filepath" "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4" "github.com/juju/worker/v4/workertest" diff --git a/internal/worker/caasapplicationprovisioner/ops.go b/internal/worker/caasapplicationprovisioner/ops.go index a73f3abaa18..35e1b261ea9 100644 --- a/internal/worker/caasapplicationprovisioner/ops.go +++ b/internal/worker/caasapplicationprovisioner/ops.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/retry" diff --git a/internal/worker/caasapplicationprovisioner/ops_test.go b/internal/worker/caasapplicationprovisioner/ops_test.go index 464be0003de..5b49388a427 100644 --- a/internal/worker/caasapplicationprovisioner/ops_test.go +++ b/internal/worker/caasapplicationprovisioner/ops_test.go @@ -4,9 +4,9 @@ package caasapplicationprovisioner_test import ( - "github.com/juju/charm/v13" "github.com/juju/clock/testclock" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/internal/worker/caasfirewaller/worker.go b/internal/worker/caasfirewaller/worker.go index dd20321eacd..3ccd12c389d 100644 --- a/internal/worker/caasfirewaller/worker.go +++ b/internal/worker/caasfirewaller/worker.go @@ -4,8 +4,8 @@ package caasfirewaller import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/worker/v4" "github.com/juju/worker/v4/catacomb" diff --git a/internal/worker/caasfirewaller/worker_test.go b/internal/worker/caasfirewaller/worker_test.go index 6adbc1d348b..72123faaaa9 100644 --- a/internal/worker/caasfirewaller/worker_test.go +++ b/internal/worker/caasfirewaller/worker_test.go @@ -4,8 +4,8 @@ package caasfirewaller_test import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4" "github.com/juju/worker/v4/workertest" diff --git a/internal/worker/firewaller/firewaller.go b/internal/worker/firewaller/firewaller.go index 61c708bba86..7995555ccb6 100644 --- a/internal/worker/firewaller/firewaller.go +++ b/internal/worker/firewaller/firewaller.go @@ -11,10 +11,10 @@ import ( "github.com/EvilSuperstars/go-cidrman" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/worker/v4" "github.com/juju/worker/v4/catacomb" diff --git a/internal/worker/uniter/api/domain_mocks.go b/internal/worker/uniter/api/domain_mocks.go index a3db476b515..6d1ce63567f 100644 --- a/internal/worker/uniter/api/domain_mocks.go +++ b/internal/worker/uniter/api/domain_mocks.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" uniter "github.com/juju/juju/api/agent/uniter" life "github.com/juju/juju/core/life" model "github.com/juju/juju/core/model" diff --git a/internal/worker/uniter/api/interface.go b/internal/worker/uniter/api/interface.go index 0b0f2dfed70..5b16524eaa5 100644 --- a/internal/worker/uniter/api/interface.go +++ b/internal/worker/uniter/api/interface.go @@ -6,7 +6,7 @@ package api import ( stdcontext "context" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/agent/uniter" diff --git a/internal/worker/uniter/charm/bundles.go b/internal/worker/uniter/charm/bundles.go index 6a65919127f..1866a35c540 100644 --- a/internal/worker/uniter/charm/bundles.go +++ b/internal/worker/uniter/charm/bundles.go @@ -8,8 +8,8 @@ import ( "os" "path" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/logger" "github.com/juju/juju/internal/downloader" diff --git a/internal/worker/uniter/charm/bundles_test.go b/internal/worker/uniter/charm/bundles_test.go index cb46a694d64..b8c1c390823 100644 --- a/internal/worker/uniter/charm/bundles_test.go +++ b/internal/worker/uniter/charm/bundles_test.go @@ -10,8 +10,8 @@ import ( "path/filepath" "regexp" - jujucharm "github.com/juju/charm/v13" "github.com/juju/errors" + jujucharm "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" diff --git a/internal/worker/uniter/charm/charm_test.go b/internal/worker/uniter/charm/charm_test.go index 36ad5135bee..dc00ef8c594 100644 --- a/internal/worker/uniter/charm/charm_test.go +++ b/internal/worker/uniter/charm/charm_test.go @@ -8,8 +8,8 @@ import ( "os" "path/filepath" - jujucharm "github.com/juju/charm/v13" "github.com/juju/collections/set" + jujucharm "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/charm/manifest_deployer.go b/internal/worker/uniter/charm/manifest_deployer.go index ab44fa37d2f..22e86ad8b8f 100644 --- a/internal/worker/uniter/charm/manifest_deployer.go +++ b/internal/worker/uniter/charm/manifest_deployer.go @@ -9,10 +9,10 @@ import ( "path/filepath" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/retry" "github.com/juju/utils/v4" diff --git a/internal/worker/uniter/container/workload.go b/internal/worker/uniter/container/workload.go index 30ffc417c80..2e51a0e045e 100644 --- a/internal/worker/uniter/container/workload.go +++ b/internal/worker/uniter/container/workload.go @@ -8,8 +8,8 @@ import ( "strconv" "sync" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/entity_mocks_test.go b/internal/worker/uniter/entity_mocks_test.go index 81426ab7a17..89b5a5dddbd 100644 --- a/internal/worker/uniter/entity_mocks_test.go +++ b/internal/worker/uniter/entity_mocks_test.go @@ -8,7 +8,7 @@ import ( "fmt" "sync" - jujucharm "github.com/juju/charm/v13" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/hook/hook.go b/internal/worker/uniter/hook/hook.go index dea77b67fba..c04ca1bf94c 100644 --- a/internal/worker/uniter/hook/hook.go +++ b/internal/worker/uniter/hook/hook.go @@ -6,8 +6,8 @@ package hook import ( "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/core/secrets" diff --git a/internal/worker/uniter/hook/hook_test.go b/internal/worker/uniter/hook/hook_test.go index f8d12c3dcd5..ea61f59cf81 100644 --- a/internal/worker/uniter/hook/hook_test.go +++ b/internal/worker/uniter/hook/hook_test.go @@ -4,7 +4,7 @@ package hook_test import ( - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/leadership/resolver.go b/internal/worker/uniter/leadership/resolver.go index d083381be3b..f124ee33491 100644 --- a/internal/worker/uniter/leadership/resolver.go +++ b/internal/worker/uniter/leadership/resolver.go @@ -6,7 +6,7 @@ package leadership import ( "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" diff --git a/internal/worker/uniter/leadership/resolver_test.go b/internal/worker/uniter/leadership/resolver_test.go index e82c76d6f1a..726e49a2a3e 100644 --- a/internal/worker/uniter/leadership/resolver_test.go +++ b/internal/worker/uniter/leadership/resolver_test.go @@ -6,7 +6,7 @@ package leadership_test import ( "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/mockrunner_test.go b/internal/worker/uniter/mockrunner_test.go index d6e4e01b53a..257107ddf4f 100644 --- a/internal/worker/uniter/mockrunner_test.go +++ b/internal/worker/uniter/mockrunner_test.go @@ -8,9 +8,9 @@ import ( "fmt" "sync" - "github.com/juju/charm/v13/hooks" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" utilexec "github.com/juju/utils/v4/exec" "github.com/juju/juju/core/status" diff --git a/internal/worker/uniter/op_callbacks.go b/internal/worker/uniter/op_callbacks.go index 0af2e2779d7..28924036c43 100644 --- a/internal/worker/uniter/op_callbacks.go +++ b/internal/worker/uniter/op_callbacks.go @@ -7,8 +7,8 @@ import ( stdcontext "context" "fmt" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/core/model" diff --git a/internal/worker/uniter/operation/deploy.go b/internal/worker/uniter/operation/deploy.go index 36e283612a6..16231642b1b 100644 --- a/internal/worker/uniter/operation/deploy.go +++ b/internal/worker/uniter/operation/deploy.go @@ -7,8 +7,8 @@ import ( "context" "fmt" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/charm" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/operation/deploy_test.go b/internal/worker/uniter/operation/deploy_test.go index 43b348033f8..1a97b0b19f5 100644 --- a/internal/worker/uniter/operation/deploy_test.go +++ b/internal/worker/uniter/operation/deploy_test.go @@ -6,8 +6,8 @@ package operation_test import ( "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/operation/executor_test.go b/internal/worker/uniter/operation/executor_test.go index e79f2b04460..2b7a3f4da74 100644 --- a/internal/worker/uniter/operation/executor_test.go +++ b/internal/worker/uniter/operation/executor_test.go @@ -7,8 +7,8 @@ import ( "context" "time" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/operation/factory_test.go b/internal/worker/uniter/operation/factory_test.go index 4c09257c100..34b7900a2dc 100644 --- a/internal/worker/uniter/operation/factory_test.go +++ b/internal/worker/uniter/operation/factory_test.go @@ -6,7 +6,7 @@ package operation_test import ( "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/operation/failaction_test.go b/internal/worker/uniter/operation/failaction_test.go index 7aa06bbab52..e439cfbcbb6 100644 --- a/internal/worker/uniter/operation/failaction_test.go +++ b/internal/worker/uniter/operation/failaction_test.go @@ -6,8 +6,8 @@ package operation_test import ( stdcontext "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/operation/leader.go b/internal/worker/uniter/operation/leader.go index 918c2f419e2..056f4b7a204 100644 --- a/internal/worker/uniter/operation/leader.go +++ b/internal/worker/uniter/operation/leader.go @@ -6,8 +6,8 @@ package operation import ( "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/operation/leader_test.go b/internal/worker/uniter/operation/leader_test.go index ab5fdd133e2..e625ab3c409 100644 --- a/internal/worker/uniter/operation/leader_test.go +++ b/internal/worker/uniter/operation/leader_test.go @@ -6,7 +6,7 @@ package operation_test import ( stdcontext "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/operation/remoteinit_test.go b/internal/worker/uniter/operation/remoteinit_test.go index f164bbbfc90..8bb8f92cd32 100644 --- a/internal/worker/uniter/operation/remoteinit_test.go +++ b/internal/worker/uniter/operation/remoteinit_test.go @@ -6,8 +6,8 @@ package operation_test import ( stdcontext "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/operation/runaction_test.go b/internal/worker/uniter/operation/runaction_test.go index 67c1b4ce358..7d1661d04e8 100644 --- a/internal/worker/uniter/operation/runaction_test.go +++ b/internal/worker/uniter/operation/runaction_test.go @@ -7,8 +7,8 @@ import ( stdcontext "context" "time" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/operation/runhook.go b/internal/worker/uniter/operation/runhook.go index 47efac9a232..5657eb5fdec 100644 --- a/internal/worker/uniter/operation/runhook.go +++ b/internal/worker/uniter/operation/runhook.go @@ -7,8 +7,8 @@ import ( stdcontext "context" "fmt" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" diff --git a/internal/worker/uniter/operation/runhook_test.go b/internal/worker/uniter/operation/runhook_test.go index d70d2e6d35d..96d58adf1a6 100644 --- a/internal/worker/uniter/operation/runhook_test.go +++ b/internal/worker/uniter/operation/runhook_test.go @@ -6,8 +6,8 @@ package operation_test import ( stdcontext "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/operation/state_test.go b/internal/worker/uniter/operation/state_test.go index 59b6c51b991..5977e240f4b 100644 --- a/internal/worker/uniter/operation/state_test.go +++ b/internal/worker/uniter/operation/state_test.go @@ -4,8 +4,8 @@ package operation_test import ( - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/operation/util_test.go b/internal/worker/uniter/operation/util_test.go index f36e67335be..0c4100a3141 100644 --- a/internal/worker/uniter/operation/util_test.go +++ b/internal/worker/uniter/operation/util_test.go @@ -7,8 +7,8 @@ import ( "context" "sync" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" utilexec "github.com/juju/utils/v4/exec" diff --git a/internal/worker/uniter/reboot/resolver.go b/internal/worker/uniter/reboot/resolver.go index 3b446909e44..9211eb6a633 100644 --- a/internal/worker/uniter/reboot/resolver.go +++ b/internal/worker/uniter/reboot/resolver.go @@ -6,8 +6,8 @@ package reboot import ( "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" diff --git a/internal/worker/uniter/relation/mock_test.go b/internal/worker/uniter/relation/mock_test.go index 2783ec5a7df..c90ed7a6e38 100644 --- a/internal/worker/uniter/relation/mock_test.go +++ b/internal/worker/uniter/relation/mock_test.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/relation/relationer.go b/internal/worker/uniter/relation/relationer.go index 9c009d9903b..78ed7e7f513 100644 --- a/internal/worker/uniter/relation/relationer.go +++ b/internal/worker/uniter/relation/relationer.go @@ -7,8 +7,8 @@ import ( stdcontext "context" "fmt" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/worker/v4/dependency" "github.com/juju/juju/core/logger" diff --git a/internal/worker/uniter/relation/relationer_test.go b/internal/worker/uniter/relation/relationer_test.go index bba645a54df..d5fe967b654 100644 --- a/internal/worker/uniter/relation/relationer_test.go +++ b/internal/worker/uniter/relation/relationer_test.go @@ -7,9 +7,9 @@ import ( "context" "fmt" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/relation/resolver.go b/internal/worker/uniter/relation/resolver.go index 6398997383d..c9434cf7dbc 100644 --- a/internal/worker/uniter/relation/resolver.go +++ b/internal/worker/uniter/relation/resolver.go @@ -6,9 +6,9 @@ package relation import ( "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/kr/pretty" diff --git a/internal/worker/uniter/relation/resolver_test.go b/internal/worker/uniter/relation/resolver_test.go index 48b3b5b42de..eea907eaca3 100644 --- a/internal/worker/uniter/relation/resolver_test.go +++ b/internal/worker/uniter/relation/resolver_test.go @@ -9,9 +9,9 @@ import ( "path/filepath" "sync/atomic" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/relation/state.go b/internal/worker/uniter/relation/state.go index bb38bef09f1..71f89475f92 100644 --- a/internal/worker/uniter/relation/state.go +++ b/internal/worker/uniter/relation/state.go @@ -6,8 +6,8 @@ package relation import ( "fmt" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/kr/pretty" "gopkg.in/yaml.v2" diff --git a/internal/worker/uniter/relation/state_test.go b/internal/worker/uniter/relation/state_test.go index d26da6567de..a59fed560cc 100644 --- a/internal/worker/uniter/relation/state_test.go +++ b/internal/worker/uniter/relation/state_test.go @@ -6,7 +6,7 @@ package relation_test import ( "fmt" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/relation/statetracker.go b/internal/worker/uniter/relation/statetracker.go index c03ddc60509..ee395aad590 100644 --- a/internal/worker/uniter/relation/statetracker.go +++ b/internal/worker/uniter/relation/statetracker.go @@ -9,9 +9,9 @@ import ( "strconv" "time" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/worker/v4" "github.com/kr/pretty" diff --git a/internal/worker/uniter/relation/statetracker_test.go b/internal/worker/uniter/relation/statetracker_test.go index aedebdfe0d3..434bc307965 100644 --- a/internal/worker/uniter/relation/statetracker_test.go +++ b/internal/worker/uniter/relation/statetracker_test.go @@ -9,9 +9,9 @@ import ( "path/filepath" "time" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/resolver.go b/internal/worker/uniter/resolver.go index 43341e224a6..90a60a542a0 100644 --- a/internal/worker/uniter/resolver.go +++ b/internal/worker/uniter/resolver.go @@ -7,9 +7,9 @@ import ( stdcontext "context" "fmt" - jujucharm "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + jujucharm "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" diff --git a/internal/worker/uniter/resolver/locker_test.go b/internal/worker/uniter/resolver/locker_test.go index 699ff26da1e..536b6190ebd 100644 --- a/internal/worker/uniter/resolver/locker_test.go +++ b/internal/worker/uniter/resolver/locker_test.go @@ -4,7 +4,7 @@ package resolver_test import ( - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/resolver/loop.go b/internal/worker/uniter/resolver/loop.go index 496d897ccb4..08e6279d1ba 100644 --- a/internal/worker/uniter/resolver/loop.go +++ b/internal/worker/uniter/resolver/loop.go @@ -7,9 +7,9 @@ import ( "context" "time" - jujucharm "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + jujucharm "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/mutex/v2" "github.com/juju/juju/core/logger" diff --git a/internal/worker/uniter/resolver/loop_test.go b/internal/worker/uniter/resolver/loop_test.go index afbd13f7f78..ced20e0a63e 100644 --- a/internal/worker/uniter/resolver/loop_test.go +++ b/internal/worker/uniter/resolver/loop_test.go @@ -8,7 +8,7 @@ import ( "errors" "time" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/mutex/v2" envtesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/resolver/opfactory.go b/internal/worker/uniter/resolver/opfactory.go index 79d78d22bfc..8435e0f754d 100644 --- a/internal/worker/uniter/resolver/opfactory.go +++ b/internal/worker/uniter/resolver/opfactory.go @@ -6,8 +6,8 @@ package resolver import ( "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/model" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/resolver/opfactory_test.go b/internal/worker/uniter/resolver/opfactory_test.go index 0df3cd50fe3..4b74400dd40 100644 --- a/internal/worker/uniter/resolver/opfactory_test.go +++ b/internal/worker/uniter/resolver/opfactory_test.go @@ -7,7 +7,7 @@ import ( "context" "errors" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/resolver_test.go b/internal/worker/uniter/resolver_test.go index 600978f64d8..5b37707bd77 100644 --- a/internal/worker/uniter/resolver_test.go +++ b/internal/worker/uniter/resolver_test.go @@ -7,8 +7,8 @@ import ( "context" "fmt" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/runner/context/context.go b/internal/worker/uniter/runner/context/context.go index 81c35b60933..f5b495f60f8 100644 --- a/internal/worker/uniter/runner/context/context.go +++ b/internal/worker/uniter/runner/context/context.go @@ -12,9 +12,9 @@ import ( "sync" "time" - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/proxy" diff --git a/internal/worker/uniter/runner/context/context_test.go b/internal/worker/uniter/runner/context/context_test.go index ecc0a178489..29729478ba2 100644 --- a/internal/worker/uniter/runner/context/context_test.go +++ b/internal/worker/uniter/runner/context/context_test.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/runner/context/contextfactory.go b/internal/worker/uniter/runner/context/contextfactory.go index cbb1f29455a..d74f93cc367 100644 --- a/internal/worker/uniter/runner/context/contextfactory.go +++ b/internal/worker/uniter/runner/context/contextfactory.go @@ -9,8 +9,8 @@ import ( "math/rand" "time" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/api/types" diff --git a/internal/worker/uniter/runner/context/contextfactory_test.go b/internal/worker/uniter/runner/context/contextfactory_test.go index 8183a97135b..10c5a654a48 100644 --- a/internal/worker/uniter/runner/context/contextfactory_test.go +++ b/internal/worker/uniter/runner/context/contextfactory_test.go @@ -7,8 +7,8 @@ import ( stdcontext "context" "time" - "github.com/juju/charm/v13/hooks" "github.com/juju/clock/testclock" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/runner/context/payloads/context_test.go b/internal/worker/uniter/runner/context/payloads/context_test.go index 92479ca676f..98bfb62af71 100644 --- a/internal/worker/uniter/runner/context/payloads/context_test.go +++ b/internal/worker/uniter/runner/context/payloads/context_test.go @@ -4,7 +4,7 @@ package payloads_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/runner/context/relation_test.go b/internal/worker/uniter/runner/context/relation_test.go index 0381032a852..14ddc9762ba 100644 --- a/internal/worker/uniter/runner/context/relation_test.go +++ b/internal/worker/uniter/runner/context/relation_test.go @@ -6,7 +6,7 @@ package context_test import ( stdcontext "context" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/runner/context/resources/base_test.go b/internal/worker/uniter/runner/context/resources/base_test.go index 15601624ee6..785ec0a59bf 100644 --- a/internal/worker/uniter/runner/context/resources/base_test.go +++ b/internal/worker/uniter/runner/context/resources/base_test.go @@ -7,7 +7,7 @@ import ( "io" "time" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/runner/context/resources/content.go b/internal/worker/uniter/runner/context/resources/content.go index 617a9897e2f..89e2a80498a 100644 --- a/internal/worker/uniter/runner/context/resources/content.go +++ b/internal/worker/uniter/runner/context/resources/content.go @@ -7,8 +7,8 @@ import ( "bytes" "io" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/utils/v4" ) diff --git a/internal/worker/uniter/runner/context/resources/content_test.go b/internal/worker/uniter/runner/context/resources/content_test.go index 58c628f311f..e3c11016b21 100644 --- a/internal/worker/uniter/runner/context/resources/content_test.go +++ b/internal/worker/uniter/runner/context/resources/content_test.go @@ -7,7 +7,7 @@ import ( "io" "strings" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/runner/context/resources/context.go b/internal/worker/uniter/runner/context/resources/context.go index 2acf87bff22..d1c290d133b 100644 --- a/internal/worker/uniter/runner/context/resources/context.go +++ b/internal/worker/uniter/runner/context/resources/context.go @@ -9,8 +9,8 @@ import ( "os" "path/filepath" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/core/logger" ) diff --git a/internal/worker/uniter/runner/context/resources/resource.go b/internal/worker/uniter/runner/context/resources/resource.go index c22ff52be4c..a0cab1c3fd5 100644 --- a/internal/worker/uniter/runner/context/resources/resource.go +++ b/internal/worker/uniter/runner/context/resources/resource.go @@ -8,8 +8,8 @@ import ( "context" "io" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "gopkg.in/httprequest.v1" "gopkg.in/yaml.v2" diff --git a/internal/worker/uniter/runner/context/util_test.go b/internal/worker/uniter/runner/context/util_test.go index 41133982355..f82e931fdc0 100644 --- a/internal/worker/uniter/runner/context/util_test.go +++ b/internal/worker/uniter/runner/context/util_test.go @@ -8,8 +8,8 @@ import ( "reflect" "time" - "github.com/juju/charm/v13" "github.com/juju/clock/testclock" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/proxy" jujutesting "github.com/juju/testing" diff --git a/internal/worker/uniter/runner/factory.go b/internal/worker/uniter/runner/factory.go index c121c891414..45c1bec903c 100644 --- a/internal/worker/uniter/runner/factory.go +++ b/internal/worker/uniter/runner/factory.go @@ -6,8 +6,8 @@ package runner import ( stdcontext "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/agent/uniter" diff --git a/internal/worker/uniter/runner/factory_test.go b/internal/worker/uniter/runner/factory_test.go index 98fcbb561ef..1caebe13c4f 100644 --- a/internal/worker/uniter/runner/factory_test.go +++ b/internal/worker/uniter/runner/factory_test.go @@ -7,7 +7,7 @@ import ( stdcontext "context" "strings" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/runner/jujuc/action-set.go b/internal/worker/uniter/runner/jujuc/action-set.go index 1a085f21e0f..759062067c4 100644 --- a/internal/worker/uniter/runner/jujuc/action-set.go +++ b/internal/worker/uniter/runner/jujuc/action-set.go @@ -7,9 +7,9 @@ import ( "fmt" "strings" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/gnuflag" + "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" ) diff --git a/internal/worker/uniter/runner/jujuc/context.go b/internal/worker/uniter/runner/jujuc/context.go index 669caca757c..9165a8bb2ee 100644 --- a/internal/worker/uniter/runner/jujuc/context.go +++ b/internal/worker/uniter/runner/jujuc/context.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/application" diff --git a/internal/worker/uniter/runner/jujuc/jujuctesting/suite.go b/internal/worker/uniter/runner/jujuc/jujuctesting/suite.go index a368341d139..358b3c5a529 100644 --- a/internal/worker/uniter/runner/jujuc/jujuctesting/suite.go +++ b/internal/worker/uniter/runner/jujuc/jujuctesting/suite.go @@ -6,7 +6,7 @@ package jujuctesting import ( "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/runner/jujuc/jujuctesting/unit.go b/internal/worker/uniter/runner/jujuc/jujuctesting/unit.go index 6b8dc3e31df..0b90691567b 100644 --- a/internal/worker/uniter/runner/jujuc/jujuctesting/unit.go +++ b/internal/worker/uniter/runner/jujuc/jujuctesting/unit.go @@ -6,8 +6,8 @@ package jujuctesting import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/application" "github.com/juju/juju/rpc/params" diff --git a/internal/worker/uniter/runner/jujuc/mocks/context_mock.go b/internal/worker/uniter/runner/jujuc/mocks/context_mock.go index 96027aeab9c..8036e43060a 100644 --- a/internal/worker/uniter/runner/jujuc/mocks/context_mock.go +++ b/internal/worker/uniter/runner/jujuc/mocks/context_mock.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" application "github.com/juju/juju/core/application" logger "github.com/juju/juju/core/logger" network "github.com/juju/juju/core/network" diff --git a/internal/worker/uniter/runner/jujuc/payload-register.go b/internal/worker/uniter/runner/jujuc/payload-register.go index 51431133422..ac76e495e2a 100644 --- a/internal/worker/uniter/runner/jujuc/payload-register.go +++ b/internal/worker/uniter/runner/jujuc/payload-register.go @@ -7,9 +7,9 @@ import ( "os" "path/filepath" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/core/payloads" diff --git a/internal/worker/uniter/runner/jujuc/payload-register_test.go b/internal/worker/uniter/runner/jujuc/payload-register_test.go index 1faec489d91..8db22e1bbd4 100644 --- a/internal/worker/uniter/runner/jujuc/payload-register_test.go +++ b/internal/worker/uniter/runner/jujuc/payload-register_test.go @@ -7,10 +7,10 @@ import ( "os" "path/filepath" - "github.com/juju/charm/v13" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/runner/jujuc/restricted.go b/internal/worker/uniter/runner/jujuc/restricted.go index f979091b2c1..d4892a51b42 100644 --- a/internal/worker/uniter/runner/jujuc/restricted.go +++ b/internal/worker/uniter/runner/jujuc/restricted.go @@ -7,8 +7,8 @@ import ( "context" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/application" diff --git a/internal/worker/uniter/runner/mocks/context_mock.go b/internal/worker/uniter/runner/mocks/context_mock.go index 518f598ee29..81d867517b4 100644 --- a/internal/worker/uniter/runner/mocks/context_mock.go +++ b/internal/worker/uniter/runner/mocks/context_mock.go @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/charm/v13" + charm "github.com/juju/juju/internal/charm" application "github.com/juju/juju/core/application" logger "github.com/juju/juju/core/logger" model "github.com/juju/juju/core/model" diff --git a/internal/worker/uniter/runner/runner_test.go b/internal/worker/uniter/runner/runner_test.go index d416841236a..a379a5db297 100644 --- a/internal/worker/uniter/runner/runner_test.go +++ b/internal/worker/uniter/runner/runner_test.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/loggo/v2" envtesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/runner/util_test.go b/internal/worker/uniter/runner/util_test.go index 055039036fa..3c85e1bc8ef 100644 --- a/internal/worker/uniter/runner/util_test.go +++ b/internal/worker/uniter/runner/util_test.go @@ -11,8 +11,8 @@ import ( "path/filepath" "time" - "github.com/juju/charm/v13" "github.com/juju/clock/testclock" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/secrets/resolver.go b/internal/worker/uniter/secrets/resolver.go index 09e47abbc5b..848b1d039c8 100644 --- a/internal/worker/uniter/secrets/resolver.go +++ b/internal/worker/uniter/secrets/resolver.go @@ -8,9 +8,9 @@ import ( "strconv" "strings" - "github.com/juju/charm/v13/hooks" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" diff --git a/internal/worker/uniter/secrets/resolver_test.go b/internal/worker/uniter/secrets/resolver_test.go index 426784b2dd0..c9b4d035e60 100644 --- a/internal/worker/uniter/secrets/resolver_test.go +++ b/internal/worker/uniter/secrets/resolver_test.go @@ -6,7 +6,7 @@ package secrets_test import ( "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/secrets/secrets_test.go b/internal/worker/uniter/secrets/secrets_test.go index 732e4541000..b759ee4d4f4 100644 --- a/internal/worker/uniter/secrets/secrets_test.go +++ b/internal/worker/uniter/secrets/secrets_test.go @@ -6,7 +6,7 @@ package secrets_test import ( "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/secrets/state.go b/internal/worker/uniter/secrets/state.go index 999ca921b1c..3b67bde8b35 100644 --- a/internal/worker/uniter/secrets/state.go +++ b/internal/worker/uniter/secrets/state.go @@ -4,9 +4,9 @@ package secrets import ( - "github.com/juju/charm/v13/hooks" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "gopkg.in/yaml.v2" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/storage/attachments.go b/internal/worker/uniter/storage/attachments.go index 66fb86c0cac..620bec8f95b 100644 --- a/internal/worker/uniter/storage/attachments.go +++ b/internal/worker/uniter/storage/attachments.go @@ -4,8 +4,8 @@ package storage import ( - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/internal/worker/uniter/api" diff --git a/internal/worker/uniter/storage/attachments_test.go b/internal/worker/uniter/storage/attachments_test.go index dd19827fbbe..77809ebb197 100644 --- a/internal/worker/uniter/storage/attachments_test.go +++ b/internal/worker/uniter/storage/attachments_test.go @@ -6,7 +6,7 @@ package storage_test import ( "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/storage/resolver.go b/internal/worker/uniter/storage/resolver.go index b87d5d3bd39..fa0e4ee2ec7 100644 --- a/internal/worker/uniter/storage/resolver.go +++ b/internal/worker/uniter/storage/resolver.go @@ -6,8 +6,8 @@ package storage import ( "context" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/core/life" diff --git a/internal/worker/uniter/storage/state.go b/internal/worker/uniter/storage/state.go index b6187ddf56c..26c1e7d5431 100644 --- a/internal/worker/uniter/storage/state.go +++ b/internal/worker/uniter/storage/state.go @@ -4,8 +4,8 @@ package storage import ( - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "gopkg.in/yaml.v2" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/storage/state_test.go b/internal/worker/uniter/storage/state_test.go index bb718e0df43..07648a703b3 100644 --- a/internal/worker/uniter/storage/state_test.go +++ b/internal/worker/uniter/storage/state_test.go @@ -4,8 +4,8 @@ package storage_test import ( - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/uniter.go b/internal/worker/uniter/uniter.go index 37048cbc821..fdd3d9ad0a0 100644 --- a/internal/worker/uniter/uniter.go +++ b/internal/worker/uniter/uniter.go @@ -9,9 +9,9 @@ import ( "os" "sync" - jujucharm "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/errors" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/utils/v4" "github.com/juju/utils/v4/exec" diff --git a/internal/worker/uniter/uniter_test.go b/internal/worker/uniter/uniter_test.go index 8ccba292c5b..6516ed47fd8 100644 --- a/internal/worker/uniter/uniter_test.go +++ b/internal/worker/uniter/uniter_test.go @@ -11,8 +11,8 @@ import ( "strings" "syscall" - "github.com/juju/charm/v13/hooks" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/loggo/v2" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" diff --git a/internal/worker/uniter/upgradeseries/resolver.go b/internal/worker/uniter/upgradeseries/resolver.go index 98184be197a..e9c3685326c 100644 --- a/internal/worker/uniter/upgradeseries/resolver.go +++ b/internal/worker/uniter/upgradeseries/resolver.go @@ -6,7 +6,7 @@ package upgradeseries import ( "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" diff --git a/internal/worker/uniter/upgradeseries/resolver_test.go b/internal/worker/uniter/upgradeseries/resolver_test.go index 3cd3ae21332..23835856ddd 100644 --- a/internal/worker/uniter/upgradeseries/resolver_test.go +++ b/internal/worker/uniter/upgradeseries/resolver_test.go @@ -6,7 +6,7 @@ package upgradeseries_test import ( "context" - "github.com/juju/charm/v13/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" diff --git a/internal/worker/uniter/util_test.go b/internal/worker/uniter/util_test.go index 2caca1a36f5..6da814eb086 100644 --- a/internal/worker/uniter/util_test.go +++ b/internal/worker/uniter/util_test.go @@ -19,10 +19,10 @@ import ( "time" pebbleclient "github.com/canonical/pebble/client" - jujucharm "github.com/juju/charm/v13" "github.com/juju/clock/testclock" "github.com/juju/collections/set" "github.com/juju/errors" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/mutex/v2" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" diff --git a/internal/worker/uniter/verifycharmprofile/verifycharmprofile.go b/internal/worker/uniter/verifycharmprofile/verifycharmprofile.go index a5a2a23b2e2..e54897d0645 100644 --- a/internal/worker/uniter/verifycharmprofile/verifycharmprofile.go +++ b/internal/worker/uniter/verifycharmprofile/verifycharmprofile.go @@ -6,7 +6,7 @@ package verifycharmprofile import ( "context" - jujucharm "github.com/juju/charm/v13" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/lxdprofile" diff --git a/juju/testing/utils.go b/juju/testing/utils.go index 4c7cfa48902..c52a41e6ce9 100644 --- a/juju/testing/utils.go +++ b/juju/testing/utils.go @@ -8,8 +8,8 @@ import ( "fmt" "os" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" gc "gopkg.in/check.v1" diff --git a/rpc/params/charms.go b/rpc/params/charms.go index 068af99913e..0577fc1b2cb 100644 --- a/rpc/params/charms.go +++ b/rpc/params/charms.go @@ -4,8 +4,8 @@ package params import ( - "github.com/juju/charm/v13" - "github.com/juju/charm/v13/assumes" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/assumes" ) // ApplicationCharmResults contains a set of ApplicationCharmResults. diff --git a/rpc/params/crossmodel.go b/rpc/params/crossmodel.go index 73a86b04f71..09dff892304 100644 --- a/rpc/params/crossmodel.go +++ b/rpc/params/crossmodel.go @@ -5,7 +5,7 @@ package params import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/kr/pretty" "gopkg.in/macaroon.v2" diff --git a/rpc/params/multiwatcher.go b/rpc/params/multiwatcher.go index f17bd8aab35..68bddfefdf1 100644 --- a/rpc/params/multiwatcher.go +++ b/rpc/params/multiwatcher.go @@ -9,8 +9,8 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/instance" diff --git a/rpc/params/params_test.go b/rpc/params/params_test.go index f9eb5ae5b13..845e5b629cf 100644 --- a/rpc/params/params_test.go +++ b/rpc/params/params_test.go @@ -7,7 +7,7 @@ import ( "encoding/json" stdtesting "testing" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/state/allwatcher.go b/state/allwatcher.go index 7b4e217f9d1..26ee3e44e99 100644 --- a/state/allwatcher.go +++ b/state/allwatcher.go @@ -7,8 +7,8 @@ import ( "reflect" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" diff --git a/state/allwatcher_internal_test.go b/state/allwatcher_internal_test.go index 86d80a4837a..2108f616468 100644 --- a/state/allwatcher_internal_test.go +++ b/state/allwatcher_internal_test.go @@ -10,8 +10,8 @@ import ( "sort" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" diff --git a/state/application.go b/state/application.go index 479ce452e77..7f12179cb90 100644 --- a/state/application.go +++ b/state/application.go @@ -11,9 +11,9 @@ import ( "strconv" "strings" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/application_test.go b/state/application_test.go index 6a4469e8082..95ad086e9d2 100644 --- a/state/application_test.go +++ b/state/application_test.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/applicationoffers.go b/state/applicationoffers.go index 7fcbea49f50..b3b1bcda91f 100644 --- a/state/applicationoffers.go +++ b/state/applicationoffers.go @@ -9,9 +9,9 @@ import ( "sort" "strings" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/applicationoffers_test.go b/state/applicationoffers_test.go index 617633619eb..f009af87871 100644 --- a/state/applicationoffers_test.go +++ b/state/applicationoffers_test.go @@ -4,8 +4,8 @@ package state_test import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" jujutxn "github.com/juju/txn/v3" diff --git a/state/charm.go b/state/charm.go index 46347afd3bb..a9d0c7be54e 100644 --- a/state/charm.go +++ b/state/charm.go @@ -9,9 +9,9 @@ import ( "regexp" "strings" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/charm_test.go b/state/charm_test.go index aa5f602d482..865601a067c 100644 --- a/state/charm_test.go +++ b/state/charm_test.go @@ -10,8 +10,8 @@ import ( "path/filepath" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" diff --git a/state/cleanup_test.go b/state/cleanup_test.go index 4a0ea25b3f0..f88937bf7a2 100644 --- a/state/cleanup_test.go +++ b/state/cleanup_test.go @@ -10,8 +10,8 @@ import ( "strconv" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/state/devices.go b/state/devices.go index a39b2adb545..36561c15f02 100644 --- a/state/devices.go +++ b/state/devices.go @@ -6,8 +6,8 @@ package state import ( "context" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/txn" diff --git a/state/endpoint.go b/state/endpoint.go index a7f7a3f09ec..d1c652259d4 100644 --- a/state/endpoint.go +++ b/state/endpoint.go @@ -6,7 +6,7 @@ package state import ( "fmt" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" ) // counterpartRole returns the RelationRole that this RelationRole diff --git a/state/endpoint_bindings.go b/state/endpoint_bindings.go index cf5cf809540..cb2becf15b2 100644 --- a/state/endpoint_bindings.go +++ b/state/endpoint_bindings.go @@ -4,9 +4,9 @@ package state import ( - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/endpoint_test.go b/state/endpoint_test.go index 87f899703f3..dfca7992f4a 100644 --- a/state/endpoint_test.go +++ b/state/endpoint_test.go @@ -4,7 +4,7 @@ package state_test import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/state/export_test.go b/state/export_test.go index e8b63def0a5..2e599b0d93d 100644 --- a/state/export_test.go +++ b/state/export_test.go @@ -10,11 +10,11 @@ import ( "strconv" "time" // Only used for time types. - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/clock/testclock" "github.com/juju/description/v6" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/filesystem.go b/state/filesystem.go index 49b512a7584..405f7414946 100644 --- a/state/filesystem.go +++ b/state/filesystem.go @@ -10,8 +10,8 @@ import ( "regexp" "strings" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/filesystem_test.go b/state/filesystem_test.go index 4269fd79713..94bdd29c56a 100644 --- a/state/filesystem_test.go +++ b/state/filesystem_test.go @@ -4,8 +4,8 @@ package state_test import ( - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" diff --git a/state/machine.go b/state/machine.go index 1219dfea51c..e32f271d25f 100644 --- a/state/machine.go +++ b/state/machine.go @@ -9,9 +9,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/migration_export.go b/state/migration_export.go index 9d19162af9a..728a493361f 100644 --- a/state/migration_export.go +++ b/state/migration_export.go @@ -8,11 +8,11 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/description/v6" "github.com/juju/errors" "github.com/juju/featureflag" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" "github.com/juju/names/v5" diff --git a/state/migration_export_test.go b/state/migration_export_test.go index 259844d3786..691812bcb52 100644 --- a/state/migration_export_test.go +++ b/state/migration_export_test.go @@ -10,10 +10,10 @@ import ( "math/rand" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/description/v6" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/state/migration_import.go b/state/migration_import.go index 6a180239126..240506939ee 100644 --- a/state/migration_import.go +++ b/state/migration_import.go @@ -10,11 +10,11 @@ import ( "reflect" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/collections/transform" "github.com/juju/description/v6" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" "github.com/juju/names/v5" diff --git a/state/migration_import_test.go b/state/migration_import_test.go index 76f9ced9565..fcde930f442 100644 --- a/state/migration_import_test.go +++ b/state/migration_import_test.go @@ -9,9 +9,9 @@ import ( "sort" "time" // only uses time.Time values - "github.com/juju/charm/v13" "github.com/juju/description/v6" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" diff --git a/state/migration_internal_test.go b/state/migration_internal_test.go index 5135887622f..dff7a565af6 100644 --- a/state/migration_internal_test.go +++ b/state/migration_internal_test.go @@ -4,8 +4,8 @@ package state import ( - "github.com/juju/charm/v13" "github.com/juju/collections/set" + "github.com/juju/juju/internal/charm" gc "gopkg.in/check.v1" "github.com/juju/juju/testing" diff --git a/state/migrations/remoteapplications.go b/state/migrations/remoteapplications.go index e811b4dec1c..1e00bc6dcde 100644 --- a/state/migrations/remoteapplications.go +++ b/state/migrations/remoteapplications.go @@ -4,9 +4,9 @@ package migrations import ( - "github.com/juju/charm/v13" "github.com/juju/description/v6" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" ) diff --git a/state/mocks/resources_mock.go b/state/mocks/resources_mock.go index edb8796676c..c78e2055ee2 100644 --- a/state/mocks/resources_mock.go +++ b/state/mocks/resources_mock.go @@ -14,7 +14,7 @@ import ( reflect "reflect" time "time" - resource "github.com/juju/charm/v13/resource" + resource "github.com/juju/juju/internal/charm/resource" resources "github.com/juju/juju/core/resources" state "github.com/juju/juju/state" gomock "go.uber.org/mock/gomock" diff --git a/state/model_test.go b/state/model_test.go index adaa4136ea1..07b6fdc3442 100644 --- a/state/model_test.go +++ b/state/model_test.go @@ -9,9 +9,9 @@ import ( "sort" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" mgotesting "github.com/juju/mgo/v3/testing" "github.com/juju/names/v5" diff --git a/state/modelgeneration.go b/state/modelgeneration.go index 79e0879bbb5..0fa3a77d1c1 100644 --- a/state/modelgeneration.go +++ b/state/modelgeneration.go @@ -9,9 +9,9 @@ import ( "strconv" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/modelgeneration_test.go b/state/modelgeneration_test.go index d51a5be0fdf..4dc79aecadd 100644 --- a/state/modelgeneration_test.go +++ b/state/modelgeneration_test.go @@ -6,9 +6,9 @@ package state_test import ( "time" - "github.com/juju/charm/v13" "github.com/juju/clock/testclock" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/state/payloads_ns.go b/state/payloads_ns.go index de6340309d7..5e120ead171 100644 --- a/state/payloads_ns.go +++ b/state/payloads_ns.go @@ -6,8 +6,8 @@ package state import ( "fmt" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/payloads_test.go b/state/payloads_test.go index 16f5a0be14c..337a1df8997 100644 --- a/state/payloads_test.go +++ b/state/payloads_test.go @@ -8,8 +8,8 @@ import ( "fmt" "sort" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/state/relation.go b/state/relation.go index 9c8a14f6bc4..07ce55abace 100644 --- a/state/relation.go +++ b/state/relation.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/relation_internal_test.go b/state/relation_internal_test.go index a6465d53ba0..386058f65d0 100644 --- a/state/relation_internal_test.go +++ b/state/relation_internal_test.go @@ -4,7 +4,7 @@ package state import ( - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" ) diff --git a/state/relation_test.go b/state/relation_test.go index 0b6f031b0e1..08137ae9c85 100644 --- a/state/relation_test.go +++ b/state/relation_test.go @@ -8,8 +8,8 @@ import ( "fmt" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" diff --git a/state/relationunit.go b/state/relationunit.go index 043755ead1e..8dde66837ed 100644 --- a/state/relationunit.go +++ b/state/relationunit.go @@ -8,9 +8,9 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/relationunit_test.go b/state/relationunit_test.go index ca5f8b62b28..2fc4540279d 100644 --- a/state/relationunit_test.go +++ b/state/relationunit_test.go @@ -9,9 +9,9 @@ import ( "strconv" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" diff --git a/state/remoteapplication.go b/state/remoteapplication.go index 2b67daeb713..955408217ff 100644 --- a/state/remoteapplication.go +++ b/state/remoteapplication.go @@ -9,9 +9,9 @@ import ( "sort" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/remoteapplication_test.go b/state/remoteapplication_test.go index ac8d1e9c7bd..dc71cf48d4a 100644 --- a/state/remoteapplication_test.go +++ b/state/remoteapplication_test.go @@ -7,8 +7,8 @@ import ( "sort" "time" - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" diff --git a/state/resources.go b/state/resources.go index d4c8988b994..ca6167baf9b 100644 --- a/state/resources.go +++ b/state/resources.go @@ -12,10 +12,10 @@ import ( "strings" "time" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/resources_staged_test.go b/state/resources_staged_test.go index 87fe2033c40..0f4479f6c9f 100644 --- a/state/resources_staged_test.go +++ b/state/resources_staged_test.go @@ -8,7 +8,7 @@ import ( "crypto/sha512" "fmt" - charmresource "github.com/juju/charm/v13/resource" + charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/state/resources_test.go b/state/resources_test.go index 283e201bab1..57a0d514c36 100644 --- a/state/resources_test.go +++ b/state/resources_test.go @@ -11,9 +11,9 @@ import ( "sort" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/state/secrets_test.go b/state/secrets_test.go index cc2267eba42..469bd7e57ee 100644 --- a/state/secrets_test.go +++ b/state/secrets_test.go @@ -8,9 +8,9 @@ import ( "sort" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" diff --git a/state/state.go b/state/state.go index 977b6a9717b..ee0ce1ba470 100644 --- a/state/state.go +++ b/state/state.go @@ -13,10 +13,10 @@ import ( "strings" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/state_test.go b/state/state_test.go index 83cbfd7bfcd..e1b915b26e2 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -12,10 +12,10 @@ import ( "sync" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/clock/testclock" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" diff --git a/state/storage.go b/state/storage.go index 4d91a9f1483..1465fed8bef 100644 --- a/state/storage.go +++ b/state/storage.go @@ -10,9 +10,9 @@ import ( "time" "github.com/dustin/go-humanize" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/storage_test.go b/state/storage_test.go index 1ae43c950be..c499e42b228 100644 --- a/state/storage_test.go +++ b/state/storage_test.go @@ -6,9 +6,9 @@ package state_test import ( "fmt" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" diff --git a/state/unit.go b/state/unit.go index f37b6a5a707..ddfc0b75f70 100644 --- a/state/unit.go +++ b/state/unit.go @@ -9,9 +9,9 @@ import ( "sort" "time" - "github.com/juju/charm/v13" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" diff --git a/state/unit_test.go b/state/unit_test.go index c1c72572ca1..553d18fd848 100644 --- a/state/unit_test.go +++ b/state/unit_test.go @@ -10,8 +10,8 @@ import ( "strings" "time" // Only used for time types. - "github.com/juju/charm/v13" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" diff --git a/state/watcher.go b/state/watcher.go index 6b7358786d1..ba32366cc63 100644 --- a/state/watcher.go +++ b/state/watcher.go @@ -14,10 +14,10 @@ import ( "sync" "time" - "github.com/juju/charm/v13" "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/names/v5" diff --git a/testcharms/charm.go b/testcharms/charm.go index d8f9eb7501d..cf3c91c1d2e 100644 --- a/testcharms/charm.go +++ b/testcharms/charm.go @@ -10,7 +10,7 @@ import ( "os" "time" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" diff --git a/testcharms/repo/repo.go b/testcharms/repo/repo.go index 8e1056ccef3..4adb9af05c0 100644 --- a/testcharms/repo/repo.go +++ b/testcharms/repo/repo.go @@ -13,7 +13,7 @@ import ( "path/filepath" "runtime" - "github.com/juju/charm/v13" + "github.com/juju/juju/internal/charm" "github.com/juju/utils/v4/fs" ) diff --git a/testing/factory/factory.go b/testing/factory/factory.go index e33edc9b51a..645d58b6cbf 100644 --- a/testing/factory/factory.go +++ b/testing/factory/factory.go @@ -10,8 +10,8 @@ import ( "sync/atomic" "time" - "github.com/juju/charm/v13" - charmresource "github.com/juju/charm/v13/resource" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" From 41156664eab14eacf9c4f3d54d1709ee98a3b922 Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Tue, 21 May 2024 15:31:05 +0100 Subject: [PATCH 2/9] Use non-deprecated function calls The charm package had fallen behind what the idomatic standard library changes. ioutil has dropped out of favour over io and os packages. This is another clear example of a library not keeping up with the times. --- internal/charm/actions.go | 6 +++--- internal/charm/charm_test.go | 5 ++--- internal/charm/charmarchive_test.go | 5 ++--- internal/charm/charmdir_test.go | 16 ++++++++-------- internal/charm/config.go | 3 +-- internal/charm/lxdprofile.go | 3 +-- internal/charm/manifest.go | 3 +-- internal/charm/meta.go | 3 +-- internal/charm/meta_test.go | 3 +-- 9 files changed, 20 insertions(+), 27 deletions(-) diff --git a/internal/charm/actions.go b/internal/charm/actions.go index 6a000b33f3d..c6ce6641b08 100644 --- a/internal/charm/actions.go +++ b/internal/charm/actions.go @@ -6,7 +6,6 @@ package charm import ( "fmt" "io" - "io/ioutil" "regexp" "strings" @@ -48,7 +47,8 @@ type ActionSpec struct { // ValidateParams validates the passed params map against the given ActionSpec // and returns any error encountered. // Usage: -// err := ch.Actions().ActionSpecs["snapshot"].ValidateParams(someMap) +// +// err := ch.Actions().ActionSpecs["snapshot"].ValidateParams(someMap) func (spec *ActionSpec) ValidateParams(params map[string]interface{}) error { // Load the schema from the Charm. specLoader := gjs.NewGoLoader(spec.Params) @@ -98,7 +98,7 @@ func (spec *ActionSpec) InsertDefaults(target map[string]interface{}) (map[strin // ReadActionsYaml builds an Actions spec from a charm's actions.yaml. func ReadActionsYaml(charmName string, r io.Reader) (*Actions, error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/internal/charm/charm_test.go b/internal/charm/charm_test.go index 1cdf7206343..c6cb7422e6b 100644 --- a/internal/charm/charm_test.go +++ b/internal/charm/charm_test.go @@ -7,7 +7,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "os" "path/filepath" @@ -51,7 +50,7 @@ func (s *CharmSuite) TestReadCharmSeriesWithoutBases(c *gc.C) { func (s *CharmSuite) TestReadCharmArchiveError(c *gc.C) { path := filepath.Join(c.MkDir(), "path") - err := ioutil.WriteFile(path, []byte("foo"), 0644) + err := os.WriteFile(path, []byte("foo"), 0644) c.Assert(err, jc.ErrorIsNil) ch, err := charm.ReadCharm(path) c.Assert(err, gc.NotNil) @@ -208,7 +207,7 @@ func checkDummy(c *gc.C, f charm.Charm, path string) { type YamlHacker map[interface{}]interface{} func ReadYaml(r io.Reader) YamlHacker { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { panic(err) } diff --git a/internal/charm/charmarchive_test.go b/internal/charm/charmarchive_test.go index 770c7eeaf26..e1ff295e901 100644 --- a/internal/charm/charmarchive_test.go +++ b/internal/charm/charmarchive_test.go @@ -7,7 +7,6 @@ import ( "archive/zip" "bytes" "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -130,7 +129,7 @@ func (s *CharmDirSuite) TestReadCharmArchiveWithJujuActions(c *gc.C) { } func (s *CharmArchiveSuite) TestReadCharmArchiveBytes(c *gc.C) { - data, err := ioutil.ReadFile(s.archivePath) + data, err := os.ReadFile(s.archivePath) c.Assert(err, jc.ErrorIsNil) archive, err := charm.ReadCharmArchiveBytes(data) @@ -371,7 +370,7 @@ func (s *CharmArchiveSuite) TestCharmArchiveRevisionFile(c *gc.C) { c.Assert(archive.Revision(), gc.Equals, 0) // Revision file with bad content - err = ioutil.WriteFile(revPath, []byte("garbage"), 0666) + err = os.WriteFile(revPath, []byte("garbage"), 0666) c.Assert(err, jc.ErrorIsNil) path := extCharmArchiveDirPath(c, charmDir) diff --git a/internal/charm/charmdir_test.go b/internal/charm/charmdir_test.go index 83d194d0027..eca5560de4d 100644 --- a/internal/charm/charmdir_test.go +++ b/internal/charm/charmdir_test.go @@ -8,7 +8,7 @@ import ( "bytes" "context" "fmt" - "io/ioutil" + "io" "os" "path/filepath" "strings" @@ -137,9 +137,9 @@ func (s *CharmDirSuite) TestArchiveToWithIgnoredFiles(c *gc.C) { _ = f.Close() // Ensure that we cannot spoof the version or revision files - err = ioutil.WriteFile(filepath.Join(dir.Path, "version"), []byte("spoofed version"), 0644) + err = os.WriteFile(filepath.Join(dir.Path, "version"), []byte("spoofed version"), 0644) c.Assert(err, jc.ErrorIsNil) - err = ioutil.WriteFile(filepath.Join(dir.Path, "revision"), []byte("42"), 0644) + err = os.WriteFile(filepath.Join(dir.Path, "revision"), []byte("42"), 0644) c.Assert(err, jc.ErrorIsNil) var b bytes.Buffer @@ -169,7 +169,7 @@ tox/** !tox/keep ` // Add .jujuignore - err := ioutil.WriteFile(filepath.Join(charmDir, ".jujuignore"), []byte(jujuignore), 0644) + err := os.WriteFile(filepath.Join(charmDir, ".jujuignore"), []byte(jujuignore), 0644) c.Assert(err, jc.ErrorIsNil) // Add directory/files that should be ignored based on jujuignore rules @@ -249,7 +249,7 @@ func (s *CharmSuite) TestArchiveToWithVersionString(c *gc.C) { c.Assert(verf, gc.NotNil) reader, err := verf.Open() c.Assert(err, jc.ErrorIsNil) - data, err := ioutil.ReadAll(reader) + data, err := io.ReadAll(reader) _ = reader.Close() c.Assert(err, jc.ErrorIsNil) @@ -404,7 +404,7 @@ func (s *CharmDirSuite) assertArchiveTo(c *gc.C, baseDir, charmDir string) { c.Assert(revf, gc.NotNil) reader, err := revf.Open() c.Assert(err, jc.ErrorIsNil) - data, err := ioutil.ReadAll(reader) + data, err := io.ReadAll(reader) reader.Close() c.Assert(err, jc.ErrorIsNil) c.Assert(string(data), gc.Equals, "1") @@ -426,7 +426,7 @@ func (s *CharmDirSuite) assertArchiveTo(c *gc.C, baseDir, charmDir string) { c.Assert(symf.Mode()&0777, gc.Equals, os.FileMode(0777)) reader, err = symf.Open() c.Assert(err, jc.ErrorIsNil) - data, err = ioutil.ReadAll(reader) + data, err = io.ReadAll(reader) reader.Close() c.Assert(err, jc.ErrorIsNil) c.Assert(string(data), gc.Equals, "../target") @@ -548,7 +548,7 @@ func (s *CharmDirSuite) TestDirRevisionFile(c *gc.C) { c.Assert(dir.Revision(), gc.Equals, 0) // Revision file with bad content - err = ioutil.WriteFile(revPath, []byte("garbage"), 0666) + err = os.WriteFile(revPath, []byte("garbage"), 0666) c.Assert(err, jc.ErrorIsNil) dir, err = charm.ReadCharmDir(charmDir) diff --git a/internal/charm/config.go b/internal/charm/config.go index 5eb05d92c34..2b9b081ac47 100644 --- a/internal/charm/config.go +++ b/internal/charm/config.go @@ -6,7 +6,6 @@ package charm import ( "fmt" "io" - "io/ioutil" "net/url" "strconv" @@ -118,7 +117,7 @@ func NewConfig() *Config { // ReadConfig reads a Config in YAML format. func ReadConfig(r io.Reader) (*Config, error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/internal/charm/lxdprofile.go b/internal/charm/lxdprofile.go index 20a468cdd8d..0fb9c6a04d2 100644 --- a/internal/charm/lxdprofile.go +++ b/internal/charm/lxdprofile.go @@ -6,7 +6,6 @@ package charm import ( "fmt" "io" - "io/ioutil" "strings" "github.com/juju/collections/set" @@ -40,7 +39,7 @@ func NewLXDProfile() *LXDProfile { // It is not validated at this point so that the caller can choose to override // any validation. func ReadLXDProfile(r io.Reader) (*LXDProfile, error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/internal/charm/manifest.go b/internal/charm/manifest.go index 9b034060406..ce7f9da9139 100644 --- a/internal/charm/manifest.go +++ b/internal/charm/manifest.go @@ -5,7 +5,6 @@ package charm import ( "io" - "io/ioutil" "github.com/juju/errors" "github.com/juju/schema" @@ -89,7 +88,7 @@ func parseBases(input interface{}) ([]Base, error) { // verification that the base.Name is a supported operating system. Full // validation done by calling Validate(). func ReadManifest(r io.Reader) (*Manifest, error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/internal/charm/meta.go b/internal/charm/meta.go index e7d910e949f..8601b26904f 100644 --- a/internal/charm/meta.go +++ b/internal/charm/meta.go @@ -6,7 +6,6 @@ package charm import ( "fmt" "io" - "io/ioutil" "regexp" "sort" "strconv" @@ -495,7 +494,7 @@ func ParseTerm(s string) (*TermsId, error) { // its representation. // The data has verified as unambiguous, but not validated. func ReadMeta(r io.Reader) (*Meta, error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err } diff --git a/internal/charm/meta_test.go b/internal/charm/meta_test.go index 9fee8907ab3..a9ebf6110f9 100644 --- a/internal/charm/meta_test.go +++ b/internal/charm/meta_test.go @@ -7,7 +7,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strings" @@ -27,7 +26,7 @@ func repoMeta(c *gc.C, name string) io.Reader { file, err := os.Open(filepath.Join(charmDir, "metadata.yaml")) c.Assert(err, gc.IsNil) defer file.Close() - data, err := ioutil.ReadAll(file) + data, err := io.ReadAll(file) c.Assert(err, gc.IsNil) return bytes.NewReader(data) } From 9b52d2b287d458b9213e6dbd9d0188fa014b033f Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Tue, 21 May 2024 16:11:20 +0100 Subject: [PATCH 3/9] Intern the fs package from utils There was a failing test when interning the charm package, as part of that investigation I needed to inspect the fs package. As this was in the juju/utils, I also brought that in. --- internal/charm/charm_test.go | 2 +- internal/charm/charmdir.go | 36 ++++++----- internal/charm/charmdir_test.go | 7 ++- internal/fs/copy.go | 108 ++++++++++++++++++++++++++++++++ internal/fs/copy_test.go | 81 ++++++++++++++++++++++++ internal/fs/package_test.go | 14 +++++ 6 files changed, 228 insertions(+), 20 deletions(-) create mode 100644 internal/fs/copy.go create mode 100644 internal/fs/copy_test.go create mode 100644 internal/fs/package_test.go diff --git a/internal/charm/charm_test.go b/internal/charm/charm_test.go index c6cb7422e6b..ae22b704b6c 100644 --- a/internal/charm/charm_test.go +++ b/internal/charm/charm_test.go @@ -12,11 +12,11 @@ import ( "github.com/juju/testing" jc "github.com/juju/testing/checkers" - "github.com/juju/utils/v4/fs" gc "gopkg.in/check.v1" "gopkg.in/yaml.v2" "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/fs" ) type CharmSuite struct { diff --git a/internal/charm/charmdir.go b/internal/charm/charmdir.go index 09828dc730b..654a80bfe7e 100644 --- a/internal/charm/charmdir.go +++ b/internal/charm/charmdir.go @@ -281,7 +281,7 @@ func writeArchive( // resolve that before creating the zip. rootPath, err := resolveSymlinkedRoot(path) if err != nil { - return err + return errors.Annotatef(err, "resolving symlinked root path") } zp := zipPacker{ Writer: zipw, @@ -296,7 +296,10 @@ func writeArchive( if versionString != "" { zp.AddFile("version", versionString) } - return filepath.Walk(rootPath, zp.WalkFunc()) + if err := filepath.Walk(rootPath, zp.WalkFunc()); err != nil { + return errors.Annotatef(err, "walking charm directory") + } + return nil } type zipPacker struct { @@ -309,7 +312,11 @@ type zipPacker struct { func (zp *zipPacker) WalkFunc() filepath.WalkFunc { return func(path string, fi os.FileInfo, err error) error { - return zp.visit(path, fi, err) + if err != nil { + return errors.Annotatef(err, "visiting %q", path) + } + + return zp.visit(path, fi) } } @@ -323,14 +330,10 @@ func (zp *zipPacker) AddFile(filename string, value string) error { return err } -func (zp *zipPacker) visit(path string, fi os.FileInfo, err error) error { - if err != nil { - return err - } - +func (zp *zipPacker) visit(path string, fi os.FileInfo) error { relpath, err := filepath.Rel(zp.root, path) if err != nil { - return err + return errors.Annotatef(err, "finding relative path for %q", path) } // Replace any Windows path separators with "/". @@ -354,7 +357,7 @@ func (zp *zipPacker) visit(path string, fi os.FileInfo, err error) error { mode := fi.Mode() if err := checkFileType(relpath, mode); err != nil { - return err + return errors.Annotatef(err, "checking file type %q", relpath) } if mode&os.ModeSymlink != 0 { method = zip.Store @@ -381,31 +384,32 @@ func (zp *zipPacker) visit(path string, fi os.FileInfo, err error) error { w, err := zp.CreateHeader(h) if err != nil || fi.IsDir() { - return err + return errors.Annotatef(err, "creating zip header for %q", relpath) } var data []byte if mode&os.ModeSymlink != 0 { target, err := os.Readlink(path) if err != nil { - return err + return errors.Annotatef(err, "reading symlink target %q", path) } if err := checkSymlinkTarget(relpath, target); err != nil { - return err + return errors.Annotatef(err, "checking symlink target %q", target) } data = []byte(target) if _, err := w.Write(data); err != nil { - return err + return errors.Annotatef(err, "writing symlink target %q", target) } + return nil } file, err := os.Open(path) if err != nil { - return err + return errors.Annotatef(err, "opening file %q", path) } defer file.Close() _, err = io.Copy(w, file) - return err + return errors.Annotatef(err, "copying file %q", path) } func checkSymlinkTarget(symlink, target string) error { diff --git a/internal/charm/charmdir_test.go b/internal/charm/charmdir_test.go index eca5560de4d..420e8c161fd 100644 --- a/internal/charm/charmdir_test.go +++ b/internal/charm/charmdir_test.go @@ -362,6 +362,7 @@ func (s *CharmDirSuite) assertArchiveTo(c *gc.C, baseDir, charmDir string) { if err := os.Symlink("../target", filepath.Join(charmDir, "hooks/symlink")); err != nil { haveSymlinks = false } + dir, err := charm.ReadCharmDir(charmDir) c.Assert(err, jc.ErrorIsNil) @@ -499,7 +500,7 @@ func (s *CharmDirSuite) TestArchiveToWithBadType(c *gc.C) { c.Assert(err, jc.ErrorIsNil) err = dir.ArchiveTo(&bytes.Buffer{}) - c.Assert(err, gc.ErrorMatches, `symlink "hooks/badfile" links out of charm: "../../target"`) + c.Assert(err, gc.ErrorMatches, `.*symlink "hooks/badfile" links out of charm: "../../target"`) // Symlink targeting an absolute path. os.Remove(badFile) @@ -510,7 +511,7 @@ func (s *CharmDirSuite) TestArchiveToWithBadType(c *gc.C) { c.Assert(err, jc.ErrorIsNil) err = dir.ArchiveTo(&bytes.Buffer{}) - c.Assert(err, gc.ErrorMatches, `symlink "hooks/badfile" is absolute: "/target"`) + c.Assert(err, gc.ErrorMatches, `.*symlink "hooks/badfile" is absolute: "/target"`) // Can't archive special files either. os.Remove(badFile) @@ -521,7 +522,7 @@ func (s *CharmDirSuite) TestArchiveToWithBadType(c *gc.C) { c.Assert(err, jc.ErrorIsNil) err = dir.ArchiveTo(&bytes.Buffer{}) - c.Assert(err, gc.ErrorMatches, `file is a named pipe: "hooks/badfile"`) + c.Assert(err, gc.ErrorMatches, `.*file is a named pipe: "hooks/badfile"`) } func (s *CharmDirSuite) TestDirRevisionFile(c *gc.C) { diff --git a/internal/fs/copy.go b/internal/fs/copy.go new file mode 100644 index 00000000000..7216530db7a --- /dev/null +++ b/internal/fs/copy.go @@ -0,0 +1,108 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package fs + +import ( + "fmt" + "io" + "os" + "path/filepath" +) + +// Copy recursively copies the file, directory or symbolic link at src +// to dst. The destination must not exist. Symbolic links are not +// followed. +// +// If the copy fails half way through, the destination might be left +// partially written. +func Copy(src, dst string) error { + srcInfo, srcErr := os.Lstat(src) + if srcErr != nil { + return srcErr + } + _, dstErr := os.Lstat(dst) + if dstErr == nil { + // TODO(rog) add a flag to permit overwriting? + return fmt.Errorf("will not overwrite %q", dst) + } + if !os.IsNotExist(dstErr) { + return dstErr + } + switch mode := srcInfo.Mode(); mode & os.ModeType { + case os.ModeSymlink: + return copySymLink(src, dst) + case os.ModeDir: + return copyDir(src, dst, mode) + case 0: + return copyFile(src, dst, mode) + default: + return fmt.Errorf("cannot copy file with mode %v", mode) + } +} + +func copySymLink(src, dst string) error { + target, err := os.Readlink(src) + if err != nil { + return err + } + return os.Symlink(target, dst) +} + +func copyFile(src, dst string, mode os.FileMode) error { + srcf, err := os.Open(src) + if err != nil { + return err + } + defer srcf.Close() + dstf, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode.Perm()) + if err != nil { + return err + } + defer dstf.Close() + // Make the actual permissions match the source permissions + // even in the presence of umask. + if err := os.Chmod(dstf.Name(), mode.Perm()); err != nil { + return err + } + if _, err := io.Copy(dstf, srcf); err != nil { + return fmt.Errorf("cannot copy %q to %q: %v", src, dst, err) + } + return nil +} + +func copyDir(src, dst string, mode os.FileMode) error { + srcf, err := os.Open(src) + if err != nil { + return err + } + defer srcf.Close() + if mode&0500 == 0 { + // The source directory doesn't have write permission, + // so give the new directory write permission anyway + // so that we have permission to create its contents. + // We'll make the permissions match at the end. + mode |= 0500 + } + if err := os.Mkdir(dst, mode.Perm()); err != nil { + return err + } + for { + names, err := srcf.Readdirnames(100) + for _, name := range names { + if err := Copy(filepath.Join(src, name), filepath.Join(dst, name)); err != nil { + return err + } + } + if err == io.EOF { + break + } + if err != nil { + return fmt.Errorf("error reading directory %q: %v", src, err) + } + } + if err := os.Chmod(dst, mode.Perm()); err != nil { + return err + } + return nil +} diff --git a/internal/fs/copy_test.go b/internal/fs/copy_test.go new file mode 100644 index 00000000000..b7f23108cf0 --- /dev/null +++ b/internal/fs/copy_test.go @@ -0,0 +1,81 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package fs + +import ( + "path/filepath" + + ft "github.com/juju/testing/filetesting" + gc "gopkg.in/check.v1" +) + +type copySuite struct{} + +var _ = gc.Suite(©Suite{}) + +var copyTests = []struct { + about string + src ft.Entries + dst ft.Entries + err string +}{{ + about: "one file", + src: []ft.Entry{ + ft.File{Path: "file", Data: "data", Perm: 0756}, + }, +}, { + about: "one directory", + src: []ft.Entry{ + ft.Dir{Path: "dir", Perm: 0777}, + }, +}, { + about: "one symlink", + src: []ft.Entry{ + ft.Symlink{Path: "link", Link: "/foo"}, + }, +}, { + about: "several entries", + src: []ft.Entry{ + ft.Dir{Path: "top", Perm: 0755}, + ft.File{Path: "top/foo", Data: "foodata", Perm: 0644}, + ft.File{Path: "top/bar", Data: "bardata", Perm: 0633}, + ft.Dir{Path: "top/next", Perm: 0721}, + ft.Symlink{Path: "top/next/link", Link: "../foo"}, + ft.File{Path: "top/next/another", Data: "anotherdata", Perm: 0644}, + }, +}, { + about: "destination already exists", + src: []ft.Entry{ + ft.Dir{Path: "dir", Perm: 0777}, + }, + dst: []ft.Entry{ + ft.Dir{Path: "dir", Perm: 0777}, + }, + err: `will not overwrite ".+dir"`, +}, { + about: "source with unwritable directory", + src: []ft.Entry{ + ft.Dir{Path: "dir", Perm: 0555}, + }, +}} + +func (*copySuite) TestCopy(c *gc.C) { + for i, test := range copyTests { + c.Logf("test %d: %v", i, test.about) + src, dst := c.MkDir(), c.MkDir() + test.src.Create(c, src) + test.dst.Create(c, dst) + path := test.src[0].GetPath() + err := Copy( + filepath.Join(src, path), + filepath.Join(dst, path), + ) + if test.err != "" { + c.Check(err, gc.ErrorMatches, test.err) + } else { + c.Assert(err, gc.IsNil) + test.src.Check(c, dst) + } + } +} diff --git a/internal/fs/package_test.go b/internal/fs/package_test.go new file mode 100644 index 00000000000..03d814b9296 --- /dev/null +++ b/internal/fs/package_test.go @@ -0,0 +1,14 @@ +// Copyright 2023 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package fs + +import ( + "testing" + + gc "gopkg.in/check.v1" +) + +func TestPackage(t *testing.T) { + gc.TestingT(t) +} From 337f0e1e95f05204384aac0a7bb7bf9640a63a4f Mon Sep 17 00:00:00 2001 From: Jack Shaw Date: Tue, 21 May 2024 16:57:19 +0100 Subject: [PATCH 4/9] Fix import ordering --- api/agent/uniter/relation.go | 2 +- api/agent/uniter/relation_test.go | 2 +- api/agent/uniter/relationunit_test.go | 2 +- api/agent/uniter/unit.go | 2 +- api/agent/uniter/unit_test.go | 2 +- api/client/application/client.go | 2 +- api/client/application/client_test.go | 2 +- api/client/applicationoffers/client.go | 2 +- api/client/applicationoffers/client_test.go | 2 +- api/client/charms/client.go | 4 +- api/client/charms/client_test.go | 4 +- api/client/charms/downloader_s3.go | 2 +- api/client/charms/localcharmclient.go | 2 +- api/client/charms/localcharmclient_test.go | 2 +- api/client/payloads/client_test.go | 2 +- api/client/payloads/helpers.go | 2 +- api/client/payloads/helpers_test.go | 2 +- api/client/resources/client.go | 2 +- api/client/resources/client_upload_test.go | 2 +- api/client/resources/helpers.go | 2 +- api/client/resources/helpers_test.go | 2 +- api/client/resources/upload.go | 2 +- api/common/charm/charmorigin.go | 2 +- api/common/charm/charmorigin_test.go | 2 +- api/common/charms/common.go | 4 +- api/common/charms/common_test.go | 4 +- .../caasapplicationprovisioner/client.go | 2 +- .../caasapplicationprovisioner/client_test.go | 2 +- api/controller/migrationmaster/client.go | 2 +- api/controller/migrationmaster/client_test.go | 2 +- apiserver/charms.go | 2 +- apiserver/charms_test.go | 2 +- apiserver/common/charms/appcharminfo_test.go | 4 +- apiserver/common/charms/charminfo_test.go | 2 +- apiserver/common/charms/common.go | 4 +- apiserver/common/crossmodel/interface.go | 2 +- apiserver/common/firewall/firewall.go | 2 +- apiserver/common/firewall/firewall_test.go | 2 +- apiserver/common/unitstatus.go | 3 +- .../facades/agent/caasapplication/state.go | 2 +- .../instancemutater/instancemutater_test.go | 2 +- .../payloadshookcontext/unitfacade_test.go | 2 +- .../facades/agent/provisioner/interface.go | 2 +- .../agent/provisioner/provisioner_test.go | 2 +- apiserver/facades/agent/provisioner/shim.go | 2 +- .../facades/agent/uniter/goal-state_test.go | 2 +- .../facades/agent/uniter/networkinfo_test.go | 2 +- .../uniter/subordinaterelationwatcher.go | 2 +- apiserver/facades/agent/uniter/uniter.go | 2 +- .../agent/uniter/uniter_network_test.go | 2 +- apiserver/facades/agent/uniter/uniter_test.go | 2 +- .../facades/client/application/application.go | 2 +- .../client/application/application_test.go | 2 +- .../application/application_unit_test.go | 4 +- .../facades/client/application/backend.go | 4 +- .../facades/client/application/deploy.go | 4 +- .../facades/client/application/deploy_test.go | 2 +- .../client/application/deployrepository.go | 4 +- .../application/deployrepository_test.go | 4 +- apiserver/facades/client/application/get.go | 2 +- .../facades/client/application/get_test.go | 2 +- .../client/application/updatebase_test.go | 2 +- .../applicationoffers_test.go | 2 +- .../client/applicationoffers/base_test.go | 2 +- .../client/applicationoffers/mock_test.go | 2 +- apiserver/facades/client/bundle/bundle.go | 4 +- .../facades/client/bundle/bundle_test.go | 4 +- apiserver/facades/client/bundle/mock_test.go | 2 +- apiserver/facades/client/bundle/state.go | 2 +- apiserver/facades/client/charms/client.go | 2 +- .../client/charms/client_integration_test.go | 2 +- .../facades/client/charms/client_test.go | 2 +- .../client/charms/clientnormalize_test.go | 2 +- .../facades/client/charms/conversions.go | 2 +- apiserver/facades/client/charms/interface.go | 2 +- .../client/charms/interfaces/downloader.go | 3 +- apiserver/facades/client/client/api_test.go | 2 +- apiserver/facades/client/client/backend.go | 2 +- .../facades/client/client/client_test.go | 2 +- apiserver/facades/client/client/status.go | 2 +- .../facades/client/client/status_test.go | 2 +- .../facades/client/controller/backend.go | 1 - .../machinemanager/machinemanager_test.go | 2 +- .../facades/client/machinemanager/state.go | 2 +- .../client/machinemanager/upgradebase_test.go | 2 +- .../client/modelgeneration/interface.go | 2 +- .../facades/client/modelgeneration/shim.go | 2 +- .../facades/client/payloads/facade_test.go | 2 +- .../facades/client/resources/base_test.go | 2 +- apiserver/facades/client/resources/facade.go | 4 +- .../facades/client/resources/repository.go | 3 +- .../resources/server_addpending_test.go | 4 +- .../resources/server_listresources_test.go | 2 +- .../facades/client/resources/server_test.go | 2 +- apiserver/facades/client/storage/mock_test.go | 2 +- .../caasapplicationprovisioner/mock_test.go | 2 +- .../caasapplicationprovisioner/provisioner.go | 2 +- .../provisioner_test.go | 4 +- .../caasapplicationprovisioner/state.go | 2 +- .../caasfirewaller/firewaller_test.go | 2 +- .../controller/caasfirewaller/mock_test.go | 2 +- .../charmdownloader/charmdownloader.go | 2 +- .../charmdownloader/charmdownloader_test.go | 2 +- .../controller/charmdownloader/interfaces.go | 2 +- .../charmrevisionupdater/charmhub.go | 2 +- .../charmrevisionupdater/interface.go | 2 +- .../charmrevisionupdater/interface_test.go | 4 +- .../charmrevisionupdater/updater.go | 4 +- .../charmrevisionupdater/updater_test.go | 4 +- .../crossmodelrelations.go | 2 +- .../crossmodelrelations_test.go | 2 +- .../remoterelations/remoterelations_test.go | 2 +- apiserver/objects.go | 2 +- apiserver/objects_test.go | 2 +- apiserver/resources.go | 2 +- apiserver/resources_mig.go | 2 +- apiserver/resources_mig_test.go | 2 +- apiserver/resources_test.go | 2 +- apiserver/testing/application.go | 2 +- caas/kubernetes/provider/bootstrap.go | 2 +- caas/kubernetes/provider/providerconfig.go | 2 +- cmd/juju/action/common.go | 2 +- cmd/juju/agree/agree/agree.go | 2 +- cmd/juju/application/bundle/bundle.go | 2 +- cmd/juju/application/bundle/bundle_test.go | 2 +- cmd/juju/application/bundle/interface.go | 3 +- cmd/juju/application/bundle_test.go | 2 +- cmd/juju/application/deploy.go | 2 +- cmd/juju/application/deploy_test.go | 4 +- cmd/juju/application/deployer/bundle.go | 2 +- cmd/juju/application/deployer/bundle_test.go | 2 +- .../application/deployer/bundlehandler.go | 4 +- .../deployer/bundlehandler_test.go | 4 +- cmd/juju/application/deployer/charm.go | 2 +- cmd/juju/application/deployer/charm_test.go | 4 +- cmd/juju/application/deployer/deployer.go | 4 +- .../application/deployer/deployer_test.go | 4 +- cmd/juju/application/deployer/interface.go | 4 +- cmd/juju/application/deployer/lxdprofile.go | 3 +- cmd/juju/application/deployer/resource.go | 2 +- cmd/juju/application/diffbundle.go | 2 +- cmd/juju/application/diffbundle_test.go | 2 +- cmd/juju/application/export_test.go | 2 +- cmd/juju/application/refresh.go | 2 +- cmd/juju/application/refresh_test.go | 4 +- cmd/juju/application/refresher/interface.go | 3 +- cmd/juju/application/refresher/refresher.go | 2 +- .../application/refresher/refresher_test.go | 2 +- cmd/juju/application/store/charmadaptor.go | 2 +- .../application/store/charmadaptor_test.go | 2 +- cmd/juju/application/store/interface.go | 3 +- cmd/juju/application/store/resolve.go | 1 + cmd/juju/application/store/store.go | 2 +- cmd/juju/application/store/store_test.go | 2 +- cmd/juju/application/utils/interface.go | 3 +- cmd/juju/application/utils/origin.go | 2 +- cmd/juju/application/utils/utils.go | 2 +- cmd/juju/application/utils/utils_test.go | 4 +- cmd/juju/charmhub/convert.go | 2 +- cmd/juju/charmhub/download.go | 2 +- cmd/juju/charmhub/info.go | 2 +- cmd/juju/charmhub/infowriter.go | 2 +- cmd/juju/charmhub/infowriter_test.go | 3 +- cmd/juju/commands/bootstrap.go | 2 +- cmd/juju/commands/helptool.go | 2 +- cmd/juju/crossmodel/find_test.go | 2 +- cmd/juju/crossmodel/list.go | 2 +- cmd/juju/crossmodel/list_test.go | 2 +- cmd/juju/crossmodel/remoteendpoints.go | 3 +- cmd/juju/crossmodel/show_test.go | 2 +- cmd/juju/payload/util_test.go | 3 +- cmd/juju/resource/charmresources.go | 4 +- cmd/juju/resource/charmresources_test.go | 4 +- cmd/juju/resource/deploy.go | 2 +- cmd/juju/resource/deploy_test.go | 2 +- cmd/juju/resource/formatter.go | 2 +- cmd/juju/resource/formatter_test.go | 2 +- cmd/juju/resource/list_test.go | 2 +- cmd/juju/resource/output_tabular_test.go | 2 +- cmd/juju/resource/resource.go | 1 + cmd/juju/resource/stub_test.go | 2 +- cmd/juju/resource/upload.go | 2 +- cmd/juju/resource/upload_test.go | 2 +- cmd/juju/resource/util_test.go | 3 +- cmd/juju/resource/validation.go | 2 +- cmd/juju/ssh/debugcode_test.go | 2 +- cmd/juju/ssh/debughooks.go | 4 +- cmd/juju/ssh/debughooks_test.go | 2 +- cmd/juju/ssh/interface.go | 2 +- cmd/juju/ssh/ssh_container_test.go | 2 +- cmd/juju/status/formatter.go | 2 +- cmd/juju/status/output_tabular.go | 4 +- cmd/juju/status/status_internal_test.go | 2 +- cmd/jujud-controller/agent/bootstrap_test.go | 2 +- core/assumes/featureset.go | 3 +- core/assumes/sat_checker.go | 1 + core/assumes/sat_checker_test.go | 3 +- core/base/base.go | 1 + core/base/base_test.go | 3 +- core/charm/charmpath.go | 2 +- core/charm/charmpath_test.go | 2 +- core/charm/computedbase.go | 2 +- core/charm/computedbase_test.go | 2 +- core/charm/format_test.go | 3 +- core/charm/origin.go | 1 + core/crossmodel/interface.go | 2 +- core/crossmodel/params.go | 3 +- core/payloads/filter_test.go | 2 +- core/payloads/payload.go | 1 + core/payloads/payload_test.go | 2 +- core/resources/application.go | 3 +- core/resources/application_test.go | 2 +- core/resources/content.go | 3 +- core/resources/resource.go | 1 + core/resources/resource_test.go | 2 +- core/resources/serialization.go | 1 + core/resources/serialization_test.go | 2 +- core/resources/testing/resource.go | 2 +- core/resources/util_test.go | 3 +- core/settings/settings.go | 1 + domain/application/service/params.go | 1 - domain/application/service/service.go | 2 +- domain/application/service/service_test.go | 2 +- domain/storage/defaults.go | 2 +- domain/storage/defaults_test.go | 2 +- domain/storage/validation.go | 2 +- domain/storage/validation_test.go | 2 +- environs/bootstrap/bootstrap.go | 2 +- environs/bootstrap/bootstrap_test.go | 2 +- internal/bootstrap/deployer.go | 2 +- internal/bootstrap/deployer_test.go | 2 +- internal/bootstrap/package_test.go | 2 +- internal/bundle/changes/changes.go | 2 +- internal/bundle/changes/changes_test.go | 2 +- internal/bundle/changes/diff.go | 2 +- internal/bundle/changes/diff_test.go | 2 +- internal/bundle/changes/handlers.go | 2 +- internal/bundle/changes/handlers_test.go | 2 +- internal/bundle/changes/model.go | 2 +- internal/bundle/changes/model_test.go | 2 +- internal/charm/assumes/parser.go | 27 +++-- internal/charm/channel.go | 15 ++- internal/charm/downloader/downloader.go | 2 +- internal/charm/downloader/downloader_test.go | 2 +- internal/charm/jujuignore.go | 1 - internal/charm/overlay.go | 102 +++++++++--------- internal/charm/repository/charmhub.go | 4 +- internal/charm/repository/charmhub_test.go | 4 +- internal/charmhub/client.go | 2 +- internal/charmhub/download.go | 2 +- .../cloudconfig/instancecfg/instancecfg.go | 2 +- internal/cloudconfig/podcfg/image.go | 2 +- internal/cloudconfig/podcfg/image_test.go | 2 +- internal/cloudconfig/userdatacfg_test.go | 2 +- internal/cloudconfig/userdatacfg_unix.go | 2 +- internal/container/broker/lxd-broker_test.go | 2 +- internal/migration/migration.go | 2 +- internal/migration/migration_test.go | 2 +- internal/resource/charmhub.go | 2 +- internal/resource/charmhub_test.go | 4 +- internal/resource/export_test.go | 2 +- internal/resource/interfaces.go | 3 +- internal/resource/opener.go | 4 +- internal/resource/opener_test.go | 4 +- internal/resource/resource.go | 1 - internal/resource/retryclient.go | 3 +- internal/worker/bootstrap/deployer.go | 2 +- internal/worker/bootstrap/worker_test.go | 2 +- .../worker/caasapplicationprovisioner/ops.go | 2 +- .../caasapplicationprovisioner/ops_test.go | 2 +- internal/worker/caasfirewaller/worker.go | 2 +- internal/worker/caasfirewaller/worker_test.go | 2 +- internal/worker/firewaller/firewaller.go | 2 +- internal/worker/uniter/api/interface.go | 2 +- internal/worker/uniter/charm/bundles.go | 2 +- internal/worker/uniter/charm/bundles_test.go | 2 +- internal/worker/uniter/charm/charm_test.go | 2 +- .../worker/uniter/charm/manifest_deployer.go | 2 +- internal/worker/uniter/container/workload.go | 2 +- internal/worker/uniter/entity_mocks_test.go | 2 +- internal/worker/uniter/hook/hook.go | 2 +- internal/worker/uniter/hook/hook_test.go | 2 +- internal/worker/uniter/leadership/resolver.go | 3 +- .../worker/uniter/leadership/resolver_test.go | 2 +- internal/worker/uniter/mockrunner_test.go | 2 +- internal/worker/uniter/op_callbacks.go | 2 +- internal/worker/uniter/operation/deploy.go | 2 +- .../worker/uniter/operation/deploy_test.go | 2 +- .../worker/uniter/operation/executor_test.go | 2 +- .../worker/uniter/operation/factory_test.go | 2 +- .../uniter/operation/failaction_test.go | 2 +- internal/worker/uniter/operation/leader.go | 2 +- .../worker/uniter/operation/leader_test.go | 2 +- .../uniter/operation/remoteinit_test.go | 2 +- .../worker/uniter/operation/runaction_test.go | 2 +- internal/worker/uniter/operation/runhook.go | 2 +- .../worker/uniter/operation/runhook_test.go | 2 +- .../worker/uniter/operation/state_test.go | 2 +- internal/worker/uniter/operation/util_test.go | 2 +- internal/worker/uniter/reboot/resolver.go | 2 +- internal/worker/uniter/relation/mock_test.go | 1 - internal/worker/uniter/relation/relationer.go | 2 +- .../worker/uniter/relation/relationer_test.go | 4 +- internal/worker/uniter/relation/resolver.go | 2 +- .../worker/uniter/relation/resolver_test.go | 4 +- internal/worker/uniter/relation/state.go | 2 +- internal/worker/uniter/relation/state_test.go | 2 +- .../worker/uniter/relation/statetracker.go | 4 +- .../uniter/relation/statetracker_test.go | 4 +- internal/worker/uniter/resolver.go | 4 +- .../worker/uniter/resolver/locker_test.go | 2 +- internal/worker/uniter/resolver/loop.go | 4 +- internal/worker/uniter/resolver/loop_test.go | 2 +- internal/worker/uniter/resolver/opfactory.go | 2 +- .../worker/uniter/resolver/opfactory_test.go | 2 +- internal/worker/uniter/resolver_test.go | 2 +- .../worker/uniter/runner/context/context.go | 4 +- .../uniter/runner/context/context_test.go | 2 +- .../uniter/runner/context/contextfactory.go | 2 +- .../runner/context/contextfactory_test.go | 2 +- .../runner/context/payloads/context_test.go | 2 +- .../uniter/runner/context/relation_test.go | 2 +- .../runner/context/resources/base_test.go | 2 +- .../runner/context/resources/content.go | 3 +- .../runner/context/resources/content_test.go | 2 +- .../runner/context/resources/context.go | 2 +- .../runner/context/resources/resource.go | 2 +- .../worker/uniter/runner/context/util_test.go | 2 +- internal/worker/uniter/runner/factory.go | 2 +- internal/worker/uniter/runner/factory_test.go | 2 +- .../worker/uniter/runner/jujuc/action-set.go | 2 +- .../worker/uniter/runner/jujuc/context.go | 2 +- .../uniter/runner/jujuc/jujuctesting/suite.go | 2 +- .../uniter/runner/jujuc/jujuctesting/unit.go | 2 +- .../uniter/runner/jujuc/payload-register.go | 2 +- .../runner/jujuc/payload-register_test.go | 2 +- .../worker/uniter/runner/jujuc/restricted.go | 2 +- internal/worker/uniter/runner/runner_test.go | 2 +- internal/worker/uniter/runner/util_test.go | 2 +- internal/worker/uniter/secrets/resolver.go | 2 +- .../worker/uniter/secrets/resolver_test.go | 2 +- .../worker/uniter/secrets/secrets_test.go | 2 +- internal/worker/uniter/secrets/state.go | 2 +- internal/worker/uniter/storage/attachments.go | 2 +- .../worker/uniter/storage/attachments_test.go | 2 +- internal/worker/uniter/storage/resolver.go | 2 +- internal/worker/uniter/storage/state.go | 2 +- internal/worker/uniter/storage/state_test.go | 2 +- internal/worker/uniter/uniter.go | 2 +- internal/worker/uniter/uniter_test.go | 2 +- .../worker/uniter/upgradeseries/resolver.go | 3 +- .../uniter/upgradeseries/resolver_test.go | 2 +- internal/worker/uniter/util_test.go | 2 +- .../verifycharmprofile/verifycharmprofile.go | 3 +- juju/testing/utils.go | 2 +- rpc/params/crossmodel.go | 2 +- rpc/params/multiwatcher.go | 2 +- rpc/params/params_test.go | 2 +- state/allwatcher.go | 2 +- state/allwatcher_internal_test.go | 2 +- state/application.go | 2 +- state/application_test.go | 2 +- state/applicationoffers.go | 2 +- state/applicationoffers_test.go | 2 +- state/charm.go | 2 +- state/charm_test.go | 2 +- state/cleanup_test.go | 2 +- state/devices.go | 2 +- state/endpoint_bindings.go | 2 +- state/endpoint_test.go | 2 +- state/export_test.go | 2 +- state/filesystem_test.go | 2 +- state/machine.go | 2 +- state/migration_export.go | 2 +- state/migration_export_test.go | 4 +- state/migration_import.go | 2 +- state/migration_import_test.go | 2 +- state/migration_internal_test.go | 2 +- state/migrations/remoteapplications.go | 3 +- state/model_test.go | 2 +- state/modelgeneration.go | 2 +- state/modelgeneration_test.go | 2 +- state/payloads_ns.go | 2 +- state/payloads_test.go | 2 +- state/relation.go | 2 +- state/relation_internal_test.go | 3 +- state/relation_test.go | 2 +- state/relationunit.go | 2 +- state/relationunit_test.go | 2 +- state/remoteapplication.go | 2 +- state/remoteapplication_test.go | 2 +- state/resources.go | 2 +- state/resources_staged_test.go | 2 +- state/resources_test.go | 4 +- state/secrets_test.go | 2 +- state/state.go | 2 +- state/state_test.go | 2 +- state/storage.go | 2 +- state/storage_test.go | 2 +- state/unit.go | 2 +- state/unit_test.go | 2 +- state/watcher.go | 2 +- testcharms/charm.go | 2 +- testcharms/repo/repo.go | 3 +- testing/factory/factory.go | 4 +- tools.go | 5 +- 406 files changed, 531 insertions(+), 531 deletions(-) diff --git a/api/agent/uniter/relation.go b/api/agent/uniter/relation.go index ead3e7efbd7..205afb0f586 100644 --- a/api/agent/uniter/relation.go +++ b/api/agent/uniter/relation.go @@ -7,11 +7,11 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/life" "github.com/juju/juju/core/relation" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/api/agent/uniter/relation_test.go b/api/agent/uniter/relation_test.go index 7cfcfcbfe49..23a97c73b85 100644 --- a/api/agent/uniter/relation_test.go +++ b/api/agent/uniter/relation_test.go @@ -6,7 +6,6 @@ package uniter_test import ( "context" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -15,6 +14,7 @@ import ( basetesting "github.com/juju/juju/api/base/testing" "github.com/juju/juju/core/life" "github.com/juju/juju/core/relation" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/testing" ) diff --git a/api/agent/uniter/relationunit_test.go b/api/agent/uniter/relationunit_test.go index e5937970080..a1586aaa2e3 100644 --- a/api/agent/uniter/relationunit_test.go +++ b/api/agent/uniter/relationunit_test.go @@ -6,7 +6,6 @@ package uniter_test import ( "context" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -15,6 +14,7 @@ import ( basetesting "github.com/juju/juju/api/base/testing" "github.com/juju/juju/core/life" "github.com/juju/juju/core/watcher/watchertest" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/testing" ) diff --git a/api/agent/uniter/unit.go b/api/agent/uniter/unit.go index 6a46f26a4f3..518284abae7 100644 --- a/api/agent/uniter/unit.go +++ b/api/agent/uniter/unit.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/common" @@ -20,6 +19,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/api/agent/uniter/unit_test.go b/api/agent/uniter/unit_test.go index 0ddce84c7a6..7791d9efa2a 100644 --- a/api/agent/uniter/unit_test.go +++ b/api/agent/uniter/unit_test.go @@ -7,7 +7,6 @@ import ( "context" "time" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -19,6 +18,7 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher/watchertest" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" coretesting "github.com/juju/juju/testing" ) diff --git a/api/client/application/client.go b/api/client/application/client.go index 783f522ca32..459cdab65ab 100644 --- a/api/client/application/client.go +++ b/api/client/application/client.go @@ -12,7 +12,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/base" @@ -22,6 +21,7 @@ import ( "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/devices" "github.com/juju/juju/core/instance" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" diff --git a/api/client/application/client_test.go b/api/client/application/client_test.go index 8004cc08774..b06232bbc9e 100644 --- a/api/client/application/client_test.go +++ b/api/client/application/client_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/instance" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" diff --git a/api/client/applicationoffers/client.go b/api/client/applicationoffers/client.go index 8b93a626b40..e5804cd4d4b 100644 --- a/api/client/applicationoffers/client.go +++ b/api/client/applicationoffers/client.go @@ -8,13 +8,13 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/base" "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/permission" "github.com/juju/juju/core/relation" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/applicationoffers/client_test.go b/api/client/applicationoffers/client_test.go index 6ea7d4fd3e9..c05f0bc0a93 100644 --- a/api/client/applicationoffers/client_test.go +++ b/api/client/applicationoffers/client_test.go @@ -9,7 +9,6 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -19,6 +18,7 @@ import ( "github.com/juju/juju/api/client/applicationoffers" apiservererrors "github.com/juju/juju/apiserver/errors" jujucrossmodel "github.com/juju/juju/core/crossmodel" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" "github.com/juju/juju/testing" diff --git a/api/client/charms/client.go b/api/client/charms/client.go index 509da21f16f..151edf7c929 100644 --- a/api/client/charms/client.go +++ b/api/client/charms/client.go @@ -8,8 +8,6 @@ import ( "github.com/juju/collections/transform" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/api/base" api "github.com/juju/juju/api/client/resources" @@ -17,6 +15,8 @@ import ( commoncharms "github.com/juju/juju/api/common/charms" apiservererrors "github.com/juju/juju/apiserver/errors" corebase "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/charms/client_test.go b/api/client/charms/client_test.go index d8ce5016ac0..a1ab1329366 100644 --- a/api/client/charms/client_test.go +++ b/api/client/charms/client_test.go @@ -7,8 +7,6 @@ import ( "os" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -18,6 +16,8 @@ import ( apicharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/core/arch" corebase "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" "github.com/juju/juju/testcharms" coretesting "github.com/juju/juju/testing" diff --git a/api/client/charms/downloader_s3.go b/api/client/charms/downloader_s3.go index 4bed91bf6f9..128405e901a 100644 --- a/api/client/charms/downloader_s3.go +++ b/api/client/charms/downloader_s3.go @@ -9,9 +9,9 @@ import ( "io" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/api/base" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/downloader" ) diff --git a/api/client/charms/localcharmclient.go b/api/client/charms/localcharmclient.go index 334a43c6d92..44a7acae97a 100644 --- a/api/client/charms/localcharmclient.go +++ b/api/client/charms/localcharmclient.go @@ -14,11 +14,11 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/version/v2" "github.com/juju/juju/api/base" "github.com/juju/juju/core/lxdprofile" + "github.com/juju/juju/internal/charm" jujuversion "github.com/juju/juju/version" ) diff --git a/api/client/charms/localcharmclient_test.go b/api/client/charms/localcharmclient_test.go index a9028406f21..5528b8d0640 100644 --- a/api/client/charms/localcharmclient_test.go +++ b/api/client/charms/localcharmclient_test.go @@ -9,7 +9,6 @@ import ( "regexp" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" "go.uber.org/mock/gomock" @@ -19,6 +18,7 @@ import ( basemocks "github.com/juju/juju/api/base/mocks" "github.com/juju/juju/api/client/charms" "github.com/juju/juju/api/http/mocks" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/testcharms" "github.com/juju/juju/testing" coretesting "github.com/juju/juju/testing" diff --git a/api/client/payloads/client_test.go b/api/client/payloads/client_test.go index d26f8710dcd..b9799789512 100644 --- a/api/client/payloads/client_test.go +++ b/api/client/payloads/client_test.go @@ -4,7 +4,6 @@ package payloads_test import ( - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -13,6 +12,7 @@ import ( "github.com/juju/juju/api/base/mocks" "github.com/juju/juju/api/client/payloads" corepayloads "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/payloads/helpers.go b/api/client/payloads/helpers.go index f40f5445440..7c35422a051 100644 --- a/api/client/payloads/helpers.go +++ b/api/client/payloads/helpers.go @@ -5,10 +5,10 @@ package payloads import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/payloads/helpers_test.go b/api/client/payloads/helpers_test.go index 0b33e164962..c2539fc260a 100644 --- a/api/client/payloads/helpers_test.go +++ b/api/client/payloads/helpers_test.go @@ -4,12 +4,12 @@ package payloads import ( - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/resources/client.go b/api/client/resources/client.go index 89737e32e6a..ac289edbe26 100644 --- a/api/client/resources/client.go +++ b/api/client/resources/client.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/api/base" @@ -17,6 +16,7 @@ import ( "github.com/juju/juju/api/http" apiservererrors "github.com/juju/juju/apiserver/errors" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/resources/client_upload_test.go b/api/client/resources/client_upload_test.go index eca55e04847..1df2a90d2f0 100644 --- a/api/client/resources/client_upload_test.go +++ b/api/client/resources/client_upload_test.go @@ -13,7 +13,6 @@ import ( "time" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" "go.uber.org/mock/gomock" @@ -26,6 +25,7 @@ import ( corebase "github.com/juju/juju/core/base" coreresources "github.com/juju/juju/core/resources" resourcetesting "github.com/juju/juju/core/resources/testing" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/uuid" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/resources/helpers.go b/api/client/resources/helpers.go index 73f1b9dc6b1..01a9bec2e2c 100644 --- a/api/client/resources/helpers.go +++ b/api/client/resources/helpers.go @@ -5,11 +5,11 @@ package resources import ( "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" apiservererrors "github.com/juju/juju/apiserver/errors" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/resources/helpers_test.go b/api/client/resources/helpers_test.go index 092495677c4..82870e8baef 100644 --- a/api/client/resources/helpers_test.go +++ b/api/client/resources/helpers_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/core/resources" resourcetesting "github.com/juju/juju/core/resources/testing" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/api/client/resources/upload.go b/api/client/resources/upload.go index 24364325a91..b310014170e 100644 --- a/api/client/resources/upload.go +++ b/api/client/resources/upload.go @@ -10,10 +10,10 @@ import ( "net/http" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) // UploadRequest defines a single upload request. diff --git a/api/common/charm/charmorigin.go b/api/common/charm/charmorigin.go index 71e7a9a28ee..bbbae4ec7c3 100644 --- a/api/common/charm/charmorigin.go +++ b/api/common/charm/charmorigin.go @@ -5,10 +5,10 @@ package charm import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/api/common/charm/charmorigin_test.go b/api/common/charm/charmorigin_test.go index 43b224a59a4..15b1286841f 100644 --- a/api/common/charm/charmorigin_test.go +++ b/api/common/charm/charmorigin_test.go @@ -4,12 +4,12 @@ package charm_test import ( - "github.com/juju/juju/internal/charm" gc "gopkg.in/check.v1" commoncharm "github.com/juju/juju/api/common/charm" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" ) type originSuite struct{} diff --git a/api/common/charms/common.go b/api/common/charms/common.go index 921e84325d1..345585b9f3a 100644 --- a/api/common/charms/common.go +++ b/api/common/charms/common.go @@ -8,12 +8,12 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/version/v2" "github.com/juju/juju/api/base" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/api/common/charms/common_test.go b/api/common/charms/common_test.go index b8f122e03e8..da277b449d9 100644 --- a/api/common/charms/common_test.go +++ b/api/common/charms/common_test.go @@ -4,14 +4,14 @@ package charms_test import ( - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/version/v2" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" basemocks "github.com/juju/juju/api/base/mocks" apicommoncharms "github.com/juju/juju/api/common/charms" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" coretesting "github.com/juju/juju/testing" ) diff --git a/api/controller/caasapplicationprovisioner/client.go b/api/controller/caasapplicationprovisioner/client.go index 8ae73990d14..6891b6f566f 100644 --- a/api/controller/caasapplicationprovisioner/client.go +++ b/api/controller/caasapplicationprovisioner/client.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/version/v2" @@ -24,6 +23,7 @@ import ( "github.com/juju/juju/core/resources" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" ) diff --git a/api/controller/caasapplicationprovisioner/client_test.go b/api/controller/caasapplicationprovisioner/client_test.go index 4ff3c3e0c1e..d2ac82fc5f3 100644 --- a/api/controller/caasapplicationprovisioner/client_test.go +++ b/api/controller/caasapplicationprovisioner/client_test.go @@ -5,7 +5,6 @@ package caasapplicationprovisioner_test import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -18,6 +17,7 @@ import ( "github.com/juju/juju/core/life" "github.com/juju/juju/core/resources" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/api/controller/migrationmaster/client.go b/api/controller/migrationmaster/client.go index ab762fdc9f4..07508fc61c0 100644 --- a/api/controller/migrationmaster/client.go +++ b/api/controller/migrationmaster/client.go @@ -12,7 +12,6 @@ import ( "time" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/version/v2" "gopkg.in/httprequest.v1" @@ -23,6 +22,7 @@ import ( "github.com/juju/juju/core/migration" "github.com/juju/juju/core/resources" "github.com/juju/juju/core/watcher" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/api/controller/migrationmaster/client_test.go b/api/controller/migrationmaster/client_test.go index 2fec981c4b5..e192538321d 100644 --- a/api/controller/migrationmaster/client_test.go +++ b/api/controller/migrationmaster/client_test.go @@ -13,7 +13,6 @@ import ( "time" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -28,6 +27,7 @@ import ( "github.com/juju/juju/core/migration" "github.com/juju/juju/core/resources" "github.com/juju/juju/core/watcher" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/uuid" "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" diff --git a/apiserver/charms.go b/apiserver/charms.go index 14f80a022d3..32d82744b3b 100644 --- a/apiserver/charms.go +++ b/apiserver/charms.go @@ -21,12 +21,12 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" ziputil "github.com/juju/utils/v4/zip" "github.com/juju/juju/apiserver/common" apiservererrors "github.com/juju/juju/apiserver/errors" corelogger "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/downloader" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/rpc/params" diff --git a/apiserver/charms_test.go b/apiserver/charms_test.go index 6cdf90f14d6..b2f2430ec2d 100644 --- a/apiserver/charms_test.go +++ b/apiserver/charms_test.go @@ -17,7 +17,6 @@ import ( "os" "path/filepath" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" @@ -29,6 +28,7 @@ import ( "github.com/juju/juju/core/permission" "github.com/juju/juju/domain/access/service" "github.com/juju/juju/internal/auth" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" diff --git a/apiserver/common/charms/appcharminfo_test.go b/apiserver/common/charms/appcharminfo_test.go index 295ea84a6c6..bb99aa13d73 100644 --- a/apiserver/common/charms/appcharminfo_test.go +++ b/apiserver/common/charms/appcharminfo_test.go @@ -7,8 +7,6 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -19,6 +17,8 @@ import ( apiservererrors "github.com/juju/juju/apiserver/errors" facademocks "github.com/juju/juju/apiserver/facade/mocks" "github.com/juju/juju/core/permission" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" ) diff --git a/apiserver/common/charms/charminfo_test.go b/apiserver/common/charms/charminfo_test.go index de250bfac28..ef65ec4ef1c 100644 --- a/apiserver/common/charms/charminfo_test.go +++ b/apiserver/common/charms/charminfo_test.go @@ -7,7 +7,6 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -18,6 +17,7 @@ import ( apiservererrors "github.com/juju/juju/apiserver/errors" facademocks "github.com/juju/juju/apiserver/facade/mocks" "github.com/juju/juju/core/permission" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" ) diff --git a/apiserver/common/charms/common.go b/apiserver/common/charms/common.go index 6c8657e0be2..e2ce27f835c 100644 --- a/apiserver/common/charms/common.go +++ b/apiserver/common/charms/common.go @@ -7,12 +7,12 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/facade" "github.com/juju/juju/core/permission" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" ) diff --git a/apiserver/common/crossmodel/interface.go b/apiserver/common/crossmodel/interface.go index 5c9eab3c7e3..fced6e21b21 100644 --- a/apiserver/common/crossmodel/interface.go +++ b/apiserver/common/crossmodel/interface.go @@ -7,7 +7,6 @@ import ( "context" "time" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "gopkg.in/macaroon.v2" @@ -17,6 +16,7 @@ import ( "github.com/juju/juju/core/permission" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/common/firewall/firewall.go b/apiserver/common/firewall/firewall.go index 996cebd42f6..1df24484207 100644 --- a/apiserver/common/firewall/firewall.go +++ b/apiserver/common/firewall/firewall.go @@ -5,11 +5,11 @@ package firewall import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" apiservererrors "github.com/juju/juju/apiserver/errors" "github.com/juju/juju/apiserver/facade" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state/watcher" diff --git a/apiserver/common/firewall/firewall_test.go b/apiserver/common/firewall/firewall_test.go index a1565c1138b..c995a0e076d 100644 --- a/apiserver/common/firewall/firewall_test.go +++ b/apiserver/common/firewall/firewall_test.go @@ -4,7 +4,6 @@ package firewall_test import ( - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gomock "go.uber.org/mock/gomock" @@ -17,6 +16,7 @@ import ( "github.com/juju/juju/core/watcher" "github.com/juju/juju/core/watcher/watchertest" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" coretesting "github.com/juju/juju/testing" diff --git a/apiserver/common/unitstatus.go b/apiserver/common/unitstatus.go index 6e41e7aebf0..ac966949f8d 100644 --- a/apiserver/common/unitstatus.go +++ b/apiserver/common/unitstatus.go @@ -7,9 +7,8 @@ import ( "context" "fmt" - "github.com/juju/juju/internal/charm/hooks" - "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/state" diff --git a/apiserver/facades/agent/caasapplication/state.go b/apiserver/facades/agent/caasapplication/state.go index cbeebfd5acf..99868507a64 100644 --- a/apiserver/facades/agent/caasapplication/state.go +++ b/apiserver/facades/agent/caasapplication/state.go @@ -4,13 +4,13 @@ package caasapplication import ( - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/version/v2" "github.com/juju/juju/caas" "github.com/juju/juju/controller" "github.com/juju/juju/core/network" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/agent/instancemutater/instancemutater_test.go b/apiserver/facades/agent/instancemutater/instancemutater_test.go index 6924088b605..498058e400c 100644 --- a/apiserver/facades/agent/instancemutater/instancemutater_test.go +++ b/apiserver/facades/agent/instancemutater/instancemutater_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" coretesting "github.com/juju/testing" "go.uber.org/mock/gomock" @@ -23,6 +22,7 @@ import ( "github.com/juju/juju/core/life" "github.com/juju/juju/core/lxdprofile" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" "github.com/juju/juju/testing" diff --git a/apiserver/facades/agent/payloadshookcontext/unitfacade_test.go b/apiserver/facades/agent/payloadshookcontext/unitfacade_test.go index 92d738a5a2a..8caa6409a21 100644 --- a/apiserver/facades/agent/payloadshookcontext/unitfacade_test.go +++ b/apiserver/facades/agent/payloadshookcontext/unitfacade_test.go @@ -7,7 +7,6 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -16,6 +15,7 @@ import ( apiservererrors "github.com/juju/juju/apiserver/errors" unitfacade "github.com/juju/juju/apiserver/facades/agent/payloadshookcontext" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/agent/provisioner/interface.go b/apiserver/facades/agent/provisioner/interface.go index cec4cc2e845..3df843eb6be 100644 --- a/apiserver/facades/agent/provisioner/interface.go +++ b/apiserver/facades/agent/provisioner/interface.go @@ -4,11 +4,11 @@ package provisioner import ( - jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/instance" corenetwork "github.com/juju/juju/core/network" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/network" "github.com/juju/juju/internal/network/containerizer" ) diff --git a/apiserver/facades/agent/provisioner/provisioner_test.go b/apiserver/facades/agent/provisioner/provisioner_test.go index 4f37989b18e..b1496c89de6 100644 --- a/apiserver/facades/agent/provisioner/provisioner_test.go +++ b/apiserver/facades/agent/provisioner/provisioner_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/proxy" jc "github.com/juju/testing/checkers" @@ -33,6 +32,7 @@ import ( "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/envcontext" environtesting "github.com/juju/juju/environs/testing" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/container" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/servicefactory" diff --git a/apiserver/facades/agent/provisioner/shim.go b/apiserver/facades/agent/provisioner/shim.go index b58f8e66fb6..49d44d1baa6 100644 --- a/apiserver/facades/agent/provisioner/shim.go +++ b/apiserver/facades/agent/provisioner/shim.go @@ -5,8 +5,8 @@ package provisioner import ( "github.com/juju/errors" - jujucharm "github.com/juju/juju/internal/charm" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/network/containerizer" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/agent/uniter/goal-state_test.go b/apiserver/facades/agent/uniter/goal-state_test.go index dbf1d6a38cd..4c9a51e5e91 100644 --- a/apiserver/facades/agent/uniter/goal-state_test.go +++ b/apiserver/facades/agent/uniter/goal-state_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -17,6 +16,7 @@ import ( apiservertesting "github.com/juju/juju/apiserver/testing" "github.com/juju/juju/core/status" "github.com/juju/juju/environs" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" diff --git a/apiserver/facades/agent/uniter/networkinfo_test.go b/apiserver/facades/agent/uniter/networkinfo_test.go index 57defc2c2c2..febfbb92f29 100644 --- a/apiserver/facades/agent/uniter/networkinfo_test.go +++ b/apiserver/facades/agent/uniter/networkinfo_test.go @@ -11,7 +11,6 @@ import ( "time" "github.com/juju/clock" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/retry" jc "github.com/juju/testing/checkers" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/caas/kubernetes/provider" k8stesting "github.com/juju/juju/caas/kubernetes/provider/testing" "github.com/juju/juju/core/network" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/agent/uniter/subordinaterelationwatcher.go b/apiserver/facades/agent/uniter/subordinaterelationwatcher.go index f556c1f461a..55492e3fc5f 100644 --- a/apiserver/facades/agent/uniter/subordinaterelationwatcher.go +++ b/apiserver/facades/agent/uniter/subordinaterelationwatcher.go @@ -6,10 +6,10 @@ package uniter import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/worker/v4/catacomb" corelogger "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/agent/uniter/uniter.go b/apiserver/facades/agent/uniter/uniter.go index 1b463710efe..54c0a02fff4 100644 --- a/apiserver/facades/agent/uniter/uniter.go +++ b/apiserver/facades/agent/uniter/uniter.go @@ -13,7 +13,6 @@ import ( "github.com/juju/clock" "github.com/juju/collections/transform" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/retry" @@ -33,6 +32,7 @@ import ( "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" "github.com/juju/juju/state/stateenvirons" diff --git a/apiserver/facades/agent/uniter/uniter_network_test.go b/apiserver/facades/agent/uniter/uniter_network_test.go index 5901845ef20..12319f0e975 100644 --- a/apiserver/facades/agent/uniter/uniter_network_test.go +++ b/apiserver/facades/agent/uniter/uniter_network_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -22,6 +21,7 @@ import ( "github.com/juju/juju/core/network" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/feature" "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/agent/uniter/uniter_test.go b/apiserver/facades/agent/uniter/uniter_test.go index ce4206ef01e..c3d97778a0e 100644 --- a/apiserver/facades/agent/uniter/uniter_test.go +++ b/apiserver/facades/agent/uniter/uniter_test.go @@ -10,7 +10,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" @@ -33,6 +32,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/password" _ "github.com/juju/juju/internal/secrets/provider/all" diff --git a/apiserver/facades/client/application/application.go b/apiserver/facades/client/application/application.go index 3610deed417..11ded6b3441 100644 --- a/apiserver/facades/client/application/application.go +++ b/apiserver/facades/client/application/application.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/schema" "github.com/juju/version/v2" @@ -48,6 +47,7 @@ import ( "github.com/juju/juju/environs" "github.com/juju/juju/environs/bootstrap" environsconfig "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/tools" diff --git a/apiserver/facades/client/application/application_test.go b/apiserver/facades/client/application/application_test.go index 7c30ce8f0dd..46f8041128d 100644 --- a/apiserver/facades/client/application/application_test.go +++ b/apiserver/facades/client/application/application_test.go @@ -9,7 +9,6 @@ import ( "regexp" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" @@ -29,6 +28,7 @@ import ( "github.com/juju/juju/core/network/firewall" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/environs" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/client/application/application_unit_test.go b/apiserver/facades/client/application/application_unit_test.go index 2dcb4a0e77b..d90dcf2009a 100644 --- a/apiserver/facades/client/application/application_unit_test.go +++ b/apiserver/facades/client/application/application_unit_test.go @@ -11,8 +11,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/assumes" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -41,6 +39,8 @@ import ( applicationservice "github.com/juju/juju/domain/application/service" storageerrors "github.com/juju/juju/domain/storage/errors" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/assumes" "github.com/juju/juju/internal/charmhub" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/storage" diff --git a/apiserver/facades/client/application/backend.go b/apiserver/facades/client/application/backend.go index 0d2130717c3..7b84dcf5620 100644 --- a/apiserver/facades/client/application/backend.go +++ b/apiserver/facades/client/application/backend.go @@ -8,8 +8,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/schema" "github.com/juju/version/v2" @@ -25,6 +23,8 @@ import ( "github.com/juju/juju/core/status" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/internal/tools" "github.com/juju/juju/state" diff --git a/apiserver/facades/client/application/deploy.go b/apiserver/facades/client/application/deploy.go index 5c5d6c88bcd..4dbb2d57f54 100644 --- a/apiserver/facades/client/application/deploy.go +++ b/apiserver/facades/client/application/deploy.go @@ -8,8 +8,6 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/assumes" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/common" @@ -22,6 +20,8 @@ import ( "github.com/juju/juju/core/objectstore" applicationservice "github.com/juju/juju/domain/application/service" "github.com/juju/juju/environs/bootstrap" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/assumes" "github.com/juju/juju/internal/storage" "github.com/juju/juju/state" "github.com/juju/juju/state/stateenvirons" diff --git a/apiserver/facades/client/application/deploy_test.go b/apiserver/facades/client/application/deploy_test.go index 5e06002db09..bf4d15d8efc 100644 --- a/apiserver/facades/client/application/deploy_test.go +++ b/apiserver/facades/client/application/deploy_test.go @@ -9,7 +9,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "gopkg.in/juju/environschema.v1" @@ -22,6 +21,7 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/core/network" "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/storage/provider" "github.com/juju/juju/juju/testing" diff --git a/apiserver/facades/client/application/deployrepository.go b/apiserver/facades/client/application/deployrepository.go index c1310f66757..a1462301d43 100644 --- a/apiserver/facades/client/application/deployrepository.go +++ b/apiserver/facades/client/application/deployrepository.go @@ -12,8 +12,6 @@ import ( jujuclock "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/kr/pretty" @@ -31,6 +29,8 @@ import ( applicationservice "github.com/juju/juju/domain/application/service" "github.com/juju/juju/environs/bootstrap" environsconfig "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/client/application/deployrepository_test.go b/apiserver/facades/client/application/deployrepository_test.go index a599c954671..8deeb20dcec 100644 --- a/apiserver/facades/client/application/deployrepository_test.go +++ b/apiserver/facades/client/application/deployrepository_test.go @@ -9,8 +9,6 @@ import ( "reflect" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" @@ -23,6 +21,8 @@ import ( "github.com/juju/juju/core/instance" applicationservice "github.com/juju/juju/domain/application/service" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" diff --git a/apiserver/facades/client/application/get.go b/apiserver/facades/client/application/get.go index fc86d35d985..5e88e45e531 100644 --- a/apiserver/facades/client/application/get.go +++ b/apiserver/facades/client/application/get.go @@ -6,7 +6,6 @@ package application import ( "context" - "github.com/juju/juju/internal/charm" "github.com/juju/schema" "gopkg.in/juju/environschema.v1" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/core/config" "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/client/application/get_test.go b/apiserver/facades/client/application/get_test.go index 590de5557e9..cc2ba2b0e29 100644 --- a/apiserver/facades/client/application/get_test.go +++ b/apiserver/facades/client/application/get_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "gopkg.in/juju/environschema.v1" @@ -24,6 +23,7 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/core/network" "github.com/juju/juju/environs" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" storageprovider "github.com/juju/juju/internal/storage/provider" jujutesting "github.com/juju/juju/juju/testing" diff --git a/apiserver/facades/client/application/updatebase_test.go b/apiserver/facades/client/application/updatebase_test.go index 273e5fe5851..b01da4cda74 100644 --- a/apiserver/facades/client/application/updatebase_test.go +++ b/apiserver/facades/client/application/updatebase_test.go @@ -7,13 +7,13 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" corebase "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub/transport" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/applicationoffers/applicationoffers_test.go b/apiserver/facades/client/applicationoffers/applicationoffers_test.go index e510c145fe2..7bb1141342e 100644 --- a/apiserver/facades/client/applicationoffers/applicationoffers_test.go +++ b/apiserver/facades/client/applicationoffers/applicationoffers_test.go @@ -12,7 +12,6 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" "github.com/juju/clock" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -25,6 +24,7 @@ import ( "github.com/juju/juju/core/network" "github.com/juju/juju/core/permission" "github.com/juju/juju/environs" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/uuid" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/client/applicationoffers/base_test.go b/apiserver/facades/client/applicationoffers/base_test.go index c6c3a1def8c..db33e4638f6 100644 --- a/apiserver/facades/client/applicationoffers/base_test.go +++ b/apiserver/facades/client/applicationoffers/base_test.go @@ -4,7 +4,6 @@ package applicationoffers_test import ( - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jtesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -17,6 +16,7 @@ import ( "github.com/juju/juju/core/network" "github.com/juju/juju/core/permission" "github.com/juju/juju/environs" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/uuid" "github.com/juju/juju/state" coretesting "github.com/juju/juju/testing" diff --git a/apiserver/facades/client/applicationoffers/mock_test.go b/apiserver/facades/client/applicationoffers/mock_test.go index 43498c1a30c..608920d7046 100644 --- a/apiserver/facades/client/applicationoffers/mock_test.go +++ b/apiserver/facades/client/applicationoffers/mock_test.go @@ -12,7 +12,6 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jtesting "github.com/juju/testing" "gopkg.in/macaroon.v2" @@ -28,6 +27,7 @@ import ( "github.com/juju/juju/core/status" "github.com/juju/juju/environs" "github.com/juju/juju/environs/envcontext" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/state" "github.com/juju/juju/testing" diff --git a/apiserver/facades/client/bundle/bundle.go b/apiserver/facades/client/bundle/bundle.go index afd86df714c..eb54a4b198f 100644 --- a/apiserver/facades/client/bundle/bundle.go +++ b/apiserver/facades/client/bundle/bundle.go @@ -14,8 +14,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/description/v6" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "gopkg.in/yaml.v2" @@ -34,6 +32,8 @@ import ( "github.com/juju/juju/core/permission" "github.com/juju/juju/environs/config" bundlechanges "github.com/juju/juju/internal/bundle/changes" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" diff --git a/apiserver/facades/client/bundle/bundle_test.go b/apiserver/facades/client/bundle/bundle_test.go index 802c72c4a6b..3697dce0558 100644 --- a/apiserver/facades/client/bundle/bundle_test.go +++ b/apiserver/facades/client/bundle/bundle_test.go @@ -9,8 +9,6 @@ import ( "strings" "github.com/juju/description/v6" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" @@ -22,6 +20,8 @@ import ( coreapplication "github.com/juju/juju/core/application" "github.com/juju/juju/core/network" "github.com/juju/juju/core/network/firewall" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/rpc/params" coretesting "github.com/juju/juju/testing" diff --git a/apiserver/facades/client/bundle/mock_test.go b/apiserver/facades/client/bundle/mock_test.go index f31eb8733c1..694462171a4 100644 --- a/apiserver/facades/client/bundle/mock_test.go +++ b/apiserver/facades/client/bundle/mock_test.go @@ -5,11 +5,11 @@ package bundle_test import ( "github.com/juju/description/v6" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" "github.com/juju/juju/apiserver/facades/client/bundle" "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/bundle/state.go b/apiserver/facades/client/bundle/state.go index ebc9bb09999..0a265f5233c 100644 --- a/apiserver/facades/client/bundle/state.go +++ b/apiserver/facades/client/bundle/state.go @@ -5,9 +5,9 @@ package bundle import ( "github.com/juju/description/v6" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/charms/client.go b/apiserver/facades/client/charms/client.go index 8e4901eeee6..e4a622123ce 100644 --- a/apiserver/facades/client/charms/client.go +++ b/apiserver/facades/client/charms/client.go @@ -13,7 +13,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/collections/transform" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" apiresources "github.com/juju/juju/api/client/resources" @@ -28,6 +27,7 @@ import ( "github.com/juju/juju/core/constraints" corelogger "github.com/juju/juju/core/logger" "github.com/juju/juju/core/permission" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" diff --git a/apiserver/facades/client/charms/client_integration_test.go b/apiserver/facades/client/charms/client_integration_test.go index 12b72552cb8..890c1ed8e75 100644 --- a/apiserver/facades/client/charms/client_integration_test.go +++ b/apiserver/facades/client/charms/client_integration_test.go @@ -7,12 +7,12 @@ import ( "fmt" "strings" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/api/client/charms" "github.com/juju/juju/core/permission" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/testcharms" jujuversion "github.com/juju/juju/version" diff --git a/apiserver/facades/client/charms/client_test.go b/apiserver/facades/client/charms/client_test.go index b31552a0add..988542fd352 100644 --- a/apiserver/facades/client/charms/client_test.go +++ b/apiserver/facades/client/charms/client_test.go @@ -9,7 +9,6 @@ import ( "net/url" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -26,6 +25,7 @@ import ( corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/instance" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/client/charms/clientnormalize_test.go b/apiserver/facades/client/charms/clientnormalize_test.go index ee5e915f6db..757561d8dee 100644 --- a/apiserver/facades/client/charms/clientnormalize_test.go +++ b/apiserver/facades/client/charms/clientnormalize_test.go @@ -4,12 +4,12 @@ package charms import ( - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/client/charms/conversions.go b/apiserver/facades/client/charms/conversions.go index 22fef6c1c43..832d3725fec 100644 --- a/apiserver/facades/client/charms/conversions.go +++ b/apiserver/facades/client/charms/conversions.go @@ -5,10 +5,10 @@ package charms import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/client/charms/interface.go b/apiserver/facades/client/charms/interface.go index 2bab11a4260..97f15640078 100644 --- a/apiserver/facades/client/charms/interface.go +++ b/apiserver/facades/client/charms/interface.go @@ -5,9 +5,9 @@ package charms import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/apiserver/facades/client/charms/interfaces" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/charms/interfaces/downloader.go b/apiserver/facades/client/charms/interfaces/downloader.go index baa2c431e2e..fec0d04bab0 100644 --- a/apiserver/facades/client/charms/interfaces/downloader.go +++ b/apiserver/facades/client/charms/interfaces/downloader.go @@ -6,9 +6,8 @@ package interfaces import ( "context" - "github.com/juju/juju/internal/charm" - corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" ) // Downloader defines an API for downloading and storing charms. diff --git a/apiserver/facades/client/client/api_test.go b/apiserver/facades/client/client/api_test.go index 912694d99bb..ee7dfe2b357 100644 --- a/apiserver/facades/client/client/api_test.go +++ b/apiserver/facades/client/client/api_test.go @@ -8,7 +8,6 @@ import ( "fmt" "time" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -25,6 +24,7 @@ import ( "github.com/juju/juju/domain/access/service" "github.com/juju/juju/environs/config" "github.com/juju/juju/internal/auth" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" diff --git a/apiserver/facades/client/client/backend.go b/apiserver/facades/client/client/backend.go index 49e6b1ccfed..8aac54b5bbd 100644 --- a/apiserver/facades/client/client/backend.go +++ b/apiserver/facades/client/client/backend.go @@ -7,7 +7,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/names/v5" "github.com/juju/replicaset/v3" @@ -19,6 +18,7 @@ import ( "github.com/juju/juju/core/permission" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/client/client_test.go b/apiserver/facades/client/client/client_test.go index 69e696f97bd..6ac0299ab93 100644 --- a/apiserver/facades/client/client/client_test.go +++ b/apiserver/facades/client/client/client_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" jtesting "github.com/juju/testing" @@ -35,6 +34,7 @@ import ( "github.com/juju/juju/domain/access/service" envtools "github.com/juju/juju/environs/tools" "github.com/juju/juju/internal/auth" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/docker" "github.com/juju/juju/internal/docker/registry" "github.com/juju/juju/internal/docker/registry/image" diff --git a/apiserver/facades/client/client/status.go b/apiserver/facades/client/client/status.go index 141ef872d2d..3b1ecf70e8f 100644 --- a/apiserver/facades/client/client/status.go +++ b/apiserver/facades/client/client/status.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/common" @@ -26,6 +25,7 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/core/network" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/client/status_test.go b/apiserver/facades/client/client/status_test.go index 0ad5f2391b2..caf9f124290 100644 --- a/apiserver/facades/client/client/status_test.go +++ b/apiserver/facades/client/client/status_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/juju/clock" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -26,6 +25,7 @@ import ( corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/migration" "github.com/juju/juju/core/network" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub/transport" "github.com/juju/juju/internal/feature" loggertesting "github.com/juju/juju/internal/logger/testing" diff --git a/apiserver/facades/client/controller/backend.go b/apiserver/facades/client/controller/backend.go index bbd2a895d7a..64d9641bd73 100644 --- a/apiserver/facades/client/controller/backend.go +++ b/apiserver/facades/client/controller/backend.go @@ -5,7 +5,6 @@ package controller import ( "github.com/juju/juju/internal/charm" - "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/machinemanager/machinemanager_test.go b/apiserver/facades/client/machinemanager/machinemanager_test.go index 5c4f0edd335..c8e7e590905 100644 --- a/apiserver/facades/client/machinemanager/machinemanager_test.go +++ b/apiserver/facades/client/machinemanager/machinemanager_test.go @@ -11,7 +11,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -31,6 +30,7 @@ import ( "github.com/juju/juju/core/network" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/client/machinemanager/state.go b/apiserver/facades/client/machinemanager/state.go index 84a11104a44..fa6b05a60ff 100644 --- a/apiserver/facades/client/machinemanager/state.go +++ b/apiserver/facades/client/machinemanager/state.go @@ -7,7 +7,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/common/storagecommon" @@ -19,6 +18,7 @@ import ( "github.com/juju/juju/core/status" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" "github.com/juju/juju/state/binarystorage" ) diff --git a/apiserver/facades/client/machinemanager/upgradebase_test.go b/apiserver/facades/client/machinemanager/upgradebase_test.go index 59e36ee8aa3..5b0392cac3b 100644 --- a/apiserver/facades/client/machinemanager/upgradebase_test.go +++ b/apiserver/facades/client/machinemanager/upgradebase_test.go @@ -7,7 +7,6 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -19,6 +18,7 @@ import ( corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/model" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub/transport" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/modelgeneration/interface.go b/apiserver/facades/client/modelgeneration/interface.go index 503e7ab6e92..b4e35abeeac 100644 --- a/apiserver/facades/client/modelgeneration/interface.go +++ b/apiserver/facades/client/modelgeneration/interface.go @@ -4,10 +4,10 @@ package modelgeneration import ( - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/settings" + "github.com/juju/juju/internal/charm" ) //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/package_mock.go github.com/juju/juju/apiserver/facades/client/modelgeneration State,Model,Generation,Application diff --git a/apiserver/facades/client/modelgeneration/shim.go b/apiserver/facades/client/modelgeneration/shim.go index be4f5450f55..1833e03e9cc 100644 --- a/apiserver/facades/client/modelgeneration/shim.go +++ b/apiserver/facades/client/modelgeneration/shim.go @@ -5,8 +5,8 @@ package modelgeneration import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/client/payloads/facade_test.go b/apiserver/facades/client/payloads/facade_test.go index 97b8745bb95..382070e1fc8 100644 --- a/apiserver/facades/client/payloads/facade_test.go +++ b/apiserver/facades/client/payloads/facade_test.go @@ -7,7 +7,6 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -16,6 +15,7 @@ import ( api "github.com/juju/juju/api/client/payloads" "github.com/juju/juju/apiserver/facades/client/payloads" corepayloads "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/client/resources/base_test.go b/apiserver/facades/client/resources/base_test.go index d90c6d85b20..718cda59381 100644 --- a/apiserver/facades/client/resources/base_test.go +++ b/apiserver/facades/client/resources/base_test.go @@ -6,7 +6,6 @@ package resources_test import ( "time" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/apiserver/facades/client/resources/mocks" coreresources "github.com/juju/juju/core/resources" resourcetesting "github.com/juju/juju/core/resources/testing" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/client/resources/facade.go b/apiserver/facades/client/resources/facade.go index c86660f9b15..74ac5986df3 100644 --- a/apiserver/facades/client/resources/facade.go +++ b/apiserver/facades/client/resources/facade.go @@ -7,8 +7,6 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" apiresources "github.com/juju/juju/api/client/resources" @@ -18,7 +16,9 @@ import ( corecharm "github.com/juju/juju/core/charm" corelogger "github.com/juju/juju/core/logger" "github.com/juju/juju/core/resources" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/repository" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/client/resources/repository.go b/apiserver/facades/client/resources/repository.go index 0dbcdc02f47..423af29ed9d 100644 --- a/apiserver/facades/client/resources/repository.go +++ b/apiserver/facades/client/resources/repository.go @@ -6,9 +6,8 @@ package resources import ( "context" - charmresource "github.com/juju/juju/internal/charm/resource" - corecharm "github.com/juju/juju/core/charm" + charmresource "github.com/juju/juju/internal/charm/resource" ) // NewCharmRepository defines methods required by the resources diff --git a/apiserver/facades/client/resources/server_addpending_test.go b/apiserver/facades/client/resources/server_addpending_test.go index 1458fff8527..7f7e3931ab2 100644 --- a/apiserver/facades/client/resources/server_addpending_test.go +++ b/apiserver/facades/client/resources/server_addpending_test.go @@ -7,14 +7,14 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/client/resources/server_listresources_test.go b/apiserver/facades/client/resources/server_listresources_test.go index 80be1f8fbff..d3b5ffbc49c 100644 --- a/apiserver/facades/client/resources/server_listresources_test.go +++ b/apiserver/facades/client/resources/server_listresources_test.go @@ -7,12 +7,12 @@ import ( "context" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" coreresources "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/apiserver/facades/client/resources/server_test.go b/apiserver/facades/client/resources/server_test.go index 1b6ca32104c..6c88285957a 100644 --- a/apiserver/facades/client/resources/server_test.go +++ b/apiserver/facades/client/resources/server_test.go @@ -4,11 +4,11 @@ package resources_test import ( - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/apiserver/facades/client/resources" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" ) diff --git a/apiserver/facades/client/storage/mock_test.go b/apiserver/facades/client/storage/mock_test.go index 3e4d5dbd2cb..306ea634f16 100644 --- a/apiserver/facades/client/storage/mock_test.go +++ b/apiserver/facades/client/storage/mock_test.go @@ -8,12 +8,12 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/facades/client/storage" "github.com/juju/juju/core/blockdevice" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" "github.com/juju/juju/testing" ) diff --git a/apiserver/facades/controller/caasapplicationprovisioner/mock_test.go b/apiserver/facades/controller/caasapplicationprovisioner/mock_test.go index 381552d592e..4f393afeaf1 100644 --- a/apiserver/facades/controller/caasapplicationprovisioner/mock_test.go +++ b/apiserver/facades/controller/caasapplicationprovisioner/mock_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" "gopkg.in/tomb.v2" @@ -31,6 +30,7 @@ import ( "github.com/juju/juju/core/watcher/watchertest" storageerrors "github.com/juju/juju/domain/storage/errors" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/docker" "github.com/juju/juju/internal/storage" "github.com/juju/juju/state" diff --git a/apiserver/facades/controller/caasapplicationprovisioner/provisioner.go b/apiserver/facades/controller/caasapplicationprovisioner/provisioner.go index c257bf09b24..8613351093d 100644 --- a/apiserver/facades/controller/caasapplicationprovisioner/provisioner.go +++ b/apiserver/facades/controller/caasapplicationprovisioner/provisioner.go @@ -14,7 +14,6 @@ import ( "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "gopkg.in/yaml.v2" @@ -39,6 +38,7 @@ import ( "github.com/juju/juju/environs/bootstrap" "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/tags" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/cloudconfig/podcfg" "github.com/juju/juju/internal/docker" "github.com/juju/juju/internal/resource" diff --git a/apiserver/facades/controller/caasapplicationprovisioner/provisioner_test.go b/apiserver/facades/controller/caasapplicationprovisioner/provisioner_test.go index 89d90457d0f..aa0545f98ac 100644 --- a/apiserver/facades/controller/caasapplicationprovisioner/provisioner_test.go +++ b/apiserver/facades/controller/caasapplicationprovisioner/provisioner_test.go @@ -9,8 +9,6 @@ import ( "github.com/juju/clock" "github.com/juju/clock/testclock" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -25,6 +23,8 @@ import ( "github.com/juju/juju/core/watcher/eventsource" "github.com/juju/juju/core/watcher/watchertest" envconfig "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/docker" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/controller/caasapplicationprovisioner/state.go b/apiserver/facades/controller/caasapplicationprovisioner/state.go index c83ee61b88b..bd78096c854 100644 --- a/apiserver/facades/controller/caasapplicationprovisioner/state.go +++ b/apiserver/facades/controller/caasapplicationprovisioner/state.go @@ -8,7 +8,6 @@ import ( "io" "time" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/controller" @@ -19,6 +18,7 @@ import ( "github.com/juju/juju/core/resources" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/controller/caasfirewaller/firewaller_test.go b/apiserver/facades/controller/caasfirewaller/firewaller_test.go index 9689988c3bc..fec667a666f 100644 --- a/apiserver/facades/controller/caasfirewaller/firewaller_test.go +++ b/apiserver/facades/controller/caasfirewaller/firewaller_test.go @@ -6,7 +6,6 @@ package caasfirewaller_test import ( "context" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" @@ -19,6 +18,7 @@ import ( apiservertesting "github.com/juju/juju/apiserver/testing" "github.com/juju/juju/core/life" "github.com/juju/juju/core/network" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" statetesting "github.com/juju/juju/state/testing" diff --git a/apiserver/facades/controller/caasfirewaller/mock_test.go b/apiserver/facades/controller/caasfirewaller/mock_test.go index 203ed85854d..63089977de0 100644 --- a/apiserver/facades/controller/caasfirewaller/mock_test.go +++ b/apiserver/facades/controller/caasfirewaller/mock_test.go @@ -4,7 +4,6 @@ package caasfirewaller_test import ( - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" @@ -12,6 +11,7 @@ import ( "github.com/juju/juju/apiserver/facades/controller/caasfirewaller" "github.com/juju/juju/core/config" "github.com/juju/juju/core/network" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" statetesting "github.com/juju/juju/state/testing" ) diff --git a/apiserver/facades/controller/charmdownloader/charmdownloader.go b/apiserver/facades/controller/charmdownloader/charmdownloader.go index 50420ab9c22..e3409b376e5 100644 --- a/apiserver/facades/controller/charmdownloader/charmdownloader.go +++ b/apiserver/facades/controller/charmdownloader/charmdownloader.go @@ -10,11 +10,11 @@ import ( "github.com/juju/clock" "github.com/juju/errors" "github.com/juju/http/v2" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" apiservererrors "github.com/juju/juju/apiserver/errors" corelogger "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state/watcher" diff --git a/apiserver/facades/controller/charmdownloader/charmdownloader_test.go b/apiserver/facades/controller/charmdownloader/charmdownloader_test.go index 644a5da9b5e..614d9fc8325 100644 --- a/apiserver/facades/controller/charmdownloader/charmdownloader_test.go +++ b/apiserver/facades/controller/charmdownloader/charmdownloader_test.go @@ -10,7 +10,6 @@ import ( "github.com/juju/clock/testclock" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/apiserver/facades/controller/charmdownloader/mocks" "github.com/juju/juju/core/arch" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/rpc/params" diff --git a/apiserver/facades/controller/charmdownloader/interfaces.go b/apiserver/facades/controller/charmdownloader/interfaces.go index 1daba2f04f2..3841f1718d8 100644 --- a/apiserver/facades/controller/charmdownloader/interfaces.go +++ b/apiserver/facades/controller/charmdownloader/interfaces.go @@ -6,12 +6,12 @@ package charmdownloader import ( "context" - "github.com/juju/juju/internal/charm" "github.com/juju/worker/v4" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/controller/charmrevisionupdater/charmhub.go b/apiserver/facades/controller/charmrevisionupdater/charmhub.go index 611a35cbf02..3c5132bd5fe 100644 --- a/apiserver/facades/controller/charmrevisionupdater/charmhub.go +++ b/apiserver/facades/controller/charmrevisionupdater/charmhub.go @@ -10,10 +10,10 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/core/charm/metrics" corelogger "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/charmhub/transport" ) diff --git a/apiserver/facades/controller/charmrevisionupdater/interface.go b/apiserver/facades/controller/charmrevisionupdater/interface.go index 0a9d6de240b..66bd11b69e7 100644 --- a/apiserver/facades/controller/charmrevisionupdater/interface.go +++ b/apiserver/facades/controller/charmrevisionupdater/interface.go @@ -5,12 +5,12 @@ package charmrevisionupdater import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/apiserver/common" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/controller/charmrevisionupdater/interface_test.go b/apiserver/facades/controller/charmrevisionupdater/interface_test.go index b88d1baf4cd..bc94bdc90c0 100644 --- a/apiserver/facades/controller/charmrevisionupdater/interface_test.go +++ b/apiserver/facades/controller/charmrevisionupdater/interface_test.go @@ -4,8 +4,6 @@ package charmrevisionupdater_test import ( - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -17,6 +15,8 @@ import ( charmmetrics "github.com/juju/juju/core/charm/metrics" corelogger "github.com/juju/juju/core/logger" "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/state" ) diff --git a/apiserver/facades/controller/charmrevisionupdater/updater.go b/apiserver/facades/controller/charmrevisionupdater/updater.go index 1f7dbb5c39c..01ef7d4e154 100644 --- a/apiserver/facades/controller/charmrevisionupdater/updater.go +++ b/apiserver/facades/controller/charmrevisionupdater/updater.go @@ -12,14 +12,14 @@ import ( "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" apiservererrors "github.com/juju/juju/apiserver/errors" charmmetrics "github.com/juju/juju/core/charm/metrics" corelogger "github.com/juju/juju/core/logger" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/rpc/params" "github.com/juju/juju/version" diff --git a/apiserver/facades/controller/charmrevisionupdater/updater_test.go b/apiserver/facades/controller/charmrevisionupdater/updater_test.go index b8dff2b0507..fc20fde08d6 100644 --- a/apiserver/facades/controller/charmrevisionupdater/updater_test.go +++ b/apiserver/facades/controller/charmrevisionupdater/updater_test.go @@ -9,8 +9,6 @@ import ( "github.com/juju/clock" "github.com/juju/clock/testclock" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -22,6 +20,8 @@ import ( "github.com/juju/juju/cloud" charmmetrics "github.com/juju/juju/core/charm/metrics" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub/transport" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/state" diff --git a/apiserver/facades/controller/crossmodelrelations/crossmodelrelations.go b/apiserver/facades/controller/crossmodelrelations/crossmodelrelations.go index 616b5adaf72..f9a284ae199 100644 --- a/apiserver/facades/controller/crossmodelrelations/crossmodelrelations.go +++ b/apiserver/facades/controller/crossmodelrelations/crossmodelrelations.go @@ -11,7 +11,6 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/worker/v4" "github.com/kr/pretty" @@ -30,6 +29,7 @@ import ( "github.com/juju/juju/core/secrets" corewatcher "github.com/juju/juju/core/watcher" "github.com/juju/juju/core/watcher/eventsource" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" "github.com/juju/juju/state/watcher" diff --git a/apiserver/facades/controller/crossmodelrelations/crossmodelrelations_test.go b/apiserver/facades/controller/crossmodelrelations/crossmodelrelations_test.go index af2c666452b..92b886c7fe6 100644 --- a/apiserver/facades/controller/crossmodelrelations/crossmodelrelations_test.go +++ b/apiserver/facades/controller/crossmodelrelations/crossmodelrelations_test.go @@ -13,7 +13,6 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" "github.com/juju/clock" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -34,6 +33,7 @@ import ( "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" diff --git a/apiserver/facades/controller/remoterelations/remoterelations_test.go b/apiserver/facades/controller/remoterelations/remoterelations_test.go index ff46a531ac5..40dc10f82f7 100644 --- a/apiserver/facades/controller/remoterelations/remoterelations_test.go +++ b/apiserver/facades/controller/remoterelations/remoterelations_test.go @@ -7,7 +7,6 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -23,6 +22,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/uuid" jujutesting "github.com/juju/juju/juju/testing" diff --git a/apiserver/objects.go b/apiserver/objects.go index d03054d6d13..5bf8420c9bf 100644 --- a/apiserver/objects.go +++ b/apiserver/objects.go @@ -13,10 +13,10 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/utils/v4" "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" ) diff --git a/apiserver/objects_test.go b/apiserver/objects_test.go index 9fe8dabc946..bdabef36593 100644 --- a/apiserver/objects_test.go +++ b/apiserver/objects_test.go @@ -14,7 +14,6 @@ import ( "os" "strings" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" @@ -24,6 +23,7 @@ import ( "github.com/juju/juju/core/permission" "github.com/juju/juju/domain/access/service" "github.com/juju/juju/internal/auth" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" diff --git a/apiserver/resources.go b/apiserver/resources.go index f0338659b49..93992e991ff 100644 --- a/apiserver/resources.go +++ b/apiserver/resources.go @@ -13,11 +13,11 @@ import ( "strconv" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" api "github.com/juju/juju/api/client/resources" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" ) diff --git a/apiserver/resources_mig.go b/apiserver/resources_mig.go index bbf1cabbb5e..f769cb96a68 100644 --- a/apiserver/resources_mig.go +++ b/apiserver/resources_mig.go @@ -10,10 +10,10 @@ import ( "strconv" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" ) diff --git a/apiserver/resources_mig_test.go b/apiserver/resources_mig_test.go index b960e6519d7..e197f0bf4d2 100644 --- a/apiserver/resources_mig_test.go +++ b/apiserver/resources_mig_test.go @@ -13,7 +13,6 @@ import ( "strings" "time" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -22,6 +21,7 @@ import ( "github.com/juju/juju/apiserver/mocks" apitesting "github.com/juju/juju/apiserver/testing" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/juju/testing" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" diff --git a/apiserver/resources_test.go b/apiserver/resources_test.go index 0a98b8ac219..6ed9109e523 100644 --- a/apiserver/resources_test.go +++ b/apiserver/resources_test.go @@ -15,7 +15,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -27,6 +26,7 @@ import ( apiservertesting "github.com/juju/juju/apiserver/testing" "github.com/juju/juju/core/resources" resourcetesting "github.com/juju/juju/core/resources/testing" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state" ) diff --git a/apiserver/testing/application.go b/apiserver/testing/application.go index 4f4a956a16c..3715b898f73 100644 --- a/apiserver/testing/application.go +++ b/apiserver/testing/application.go @@ -4,11 +4,11 @@ package testing import ( - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/constraints" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" coretesting "github.com/juju/juju/testing" ) diff --git a/caas/kubernetes/provider/bootstrap.go b/caas/kubernetes/provider/bootstrap.go index e5de3236e72..45c334dc42c 100644 --- a/caas/kubernetes/provider/bootstrap.go +++ b/caas/kubernetes/provider/bootstrap.go @@ -12,7 +12,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/featureflag" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" "github.com/juju/retry" @@ -44,6 +43,7 @@ import ( "github.com/juju/juju/core/watcher" "github.com/juju/juju/environs" environsbootstrap "github.com/juju/juju/environs/bootstrap" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig" "github.com/juju/juju/internal/cloudconfig/podcfg" "github.com/juju/juju/internal/docker" diff --git a/caas/kubernetes/provider/providerconfig.go b/caas/kubernetes/provider/providerconfig.go index 8493611d46f..faa06fbf76d 100644 --- a/caas/kubernetes/provider/providerconfig.go +++ b/caas/kubernetes/provider/providerconfig.go @@ -7,13 +7,13 @@ import ( "context" "fmt" - "github.com/juju/juju/internal/charm" "github.com/juju/schema" "github.com/juju/version/v2" "gopkg.in/juju/environschema.v1" k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" ) var ( diff --git a/cmd/juju/action/common.go b/cmd/juju/action/common.go index 6fcbda5e722..62b93a8eb3c 100644 --- a/cmd/juju/action/common.go +++ b/cmd/juju/action/common.go @@ -21,7 +21,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/mattn/go-isatty" "gopkg.in/yaml.v2" @@ -30,6 +29,7 @@ import ( "github.com/juju/juju/core/actions" "github.com/juju/juju/core/output" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/agree/agree/agree.go b/cmd/juju/agree/agree/agree.go index e70da75069c..1499d65f91a 100644 --- a/cmd/juju/agree/agree/agree.go +++ b/cmd/juju/agree/agree/agree.go @@ -15,12 +15,12 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" "github.com/juju/terms-client/v2/api" "github.com/juju/terms-client/v2/api/wireformat" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/internal/charm" ) var ( diff --git a/cmd/juju/application/bundle/bundle.go b/cmd/juju/application/bundle/bundle.go index 9a5f75fc2b2..f60af075ba7 100644 --- a/cmd/juju/application/bundle/bundle.go +++ b/cmd/juju/application/bundle/bundle.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "gopkg.in/yaml.v3" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/core/devices" "github.com/juju/juju/core/model" bundlechanges "github.com/juju/juju/internal/bundle/changes" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/application/bundle/bundle_test.go b/cmd/juju/application/bundle/bundle_test.go index 5be1df7aba1..f57e98e9f96 100644 --- a/cmd/juju/application/bundle/bundle_test.go +++ b/cmd/juju/application/bundle/bundle_test.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -18,6 +17,7 @@ import ( "github.com/juju/juju/core/life" "github.com/juju/juju/core/model" bundlechanges "github.com/juju/juju/internal/bundle/changes" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/application/bundle/interface.go b/cmd/juju/application/bundle/interface.go index 50911dca2f2..5241317a058 100644 --- a/cmd/juju/application/bundle/interface.go +++ b/cmd/juju/application/bundle/interface.go @@ -4,9 +4,8 @@ package bundle import ( - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/core/constraints" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/application/bundle_test.go b/cmd/juju/application/bundle_test.go index 7a91f290d67..40aaa0dfd57 100644 --- a/cmd/juju/application/bundle_test.go +++ b/cmd/juju/application/bundle_test.go @@ -11,7 +11,6 @@ import ( "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -24,6 +23,7 @@ import ( "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/crossmodel" + "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/juju/juju/testing" "github.com/juju/juju/rpc/params" "github.com/juju/juju/testcharms" diff --git a/cmd/juju/application/deploy.go b/cmd/juju/application/deploy.go index d136e2a9b5e..d0b19eb3001 100644 --- a/cmd/juju/application/deploy.go +++ b/cmd/juju/application/deploy.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/version/v2" @@ -38,6 +37,7 @@ import ( "github.com/juju/juju/core/devices" "github.com/juju/juju/core/model" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/storage" apiparams "github.com/juju/juju/rpc/params" diff --git a/cmd/juju/application/deploy_test.go b/cmd/juju/application/deploy_test.go index c09723f3fff..70af06ddc9d 100644 --- a/cmd/juju/application/deploy_test.go +++ b/cmd/juju/application/deploy_test.go @@ -19,8 +19,6 @@ import ( "github.com/juju/collections/transform" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/loggo/v2" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -51,6 +49,8 @@ import ( "github.com/juju/juju/core/devices" "github.com/juju/juju/core/instance" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/storage" "github.com/juju/juju/jujuclient" "github.com/juju/juju/jujuclient/jujuclienttesting" diff --git a/cmd/juju/application/deployer/bundle.go b/cmd/juju/application/deployer/bundle.go index c769cbdc927..bdcc5800fc3 100644 --- a/cmd/juju/application/deployer/bundle.go +++ b/cmd/juju/application/deployer/bundle.go @@ -10,12 +10,12 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/cmd/juju/application/bundle" "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/devices" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" ) diff --git a/cmd/juju/application/deployer/bundle_test.go b/cmd/juju/application/deployer/bundle_test.go index d91d3a4b89b..34dfa168970 100644 --- a/cmd/juju/application/deployer/bundle_test.go +++ b/cmd/juju/application/deployer/bundle_test.go @@ -4,11 +4,11 @@ package deployer import ( - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/constraints" + "github.com/juju/juju/internal/charm" ) type bundleSuite struct { diff --git a/cmd/juju/application/deployer/bundlehandler.go b/cmd/juju/application/deployer/bundlehandler.go index 04d11ca0867..f1d6f93c2eb 100644 --- a/cmd/juju/application/deployer/bundlehandler.go +++ b/cmd/juju/application/deployer/bundlehandler.go @@ -16,8 +16,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/kr/pretty" "gopkg.in/yaml.v2" @@ -42,6 +40,8 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/environs/config" bundlechanges "github.com/juju/juju/internal/bundle/changes" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" "github.com/juju/juju/state/watcher" diff --git a/cmd/juju/application/deployer/bundlehandler_test.go b/cmd/juju/application/deployer/bundlehandler_test.go index ee4325ee6b2..cdfbe41cdab 100644 --- a/cmd/juju/application/deployer/bundlehandler_test.go +++ b/cmd/juju/application/deployer/bundlehandler_test.go @@ -14,8 +14,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/collections/transform" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" @@ -39,6 +37,8 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/environs/config" bundlechanges "github.com/juju/juju/internal/bundle/changes" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" "github.com/juju/juju/testcharms" diff --git a/cmd/juju/application/deployer/charm.go b/cmd/juju/application/deployer/charm.go index fbf6ffad41c..376c40ecd74 100644 --- a/cmd/juju/application/deployer/charm.go +++ b/cmd/juju/application/deployer/charm.go @@ -11,7 +11,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/api/client/application" applicationapi "github.com/juju/juju/api/client/application" @@ -25,6 +24,7 @@ import ( "github.com/juju/juju/core/devices" "github.com/juju/juju/core/instance" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" ) diff --git a/cmd/juju/application/deployer/charm_test.go b/cmd/juju/application/deployer/charm_test.go index 1a7b1dc53a0..69a2c5b55cf 100644 --- a/cmd/juju/application/deployer/charm_test.go +++ b/cmd/juju/application/deployer/charm_test.go @@ -12,8 +12,6 @@ import ( "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -27,6 +25,8 @@ import ( "github.com/juju/juju/cmd/modelcmd" corebase "github.com/juju/juju/core/base" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" coretesting "github.com/juju/juju/testing" ) diff --git a/cmd/juju/application/deployer/deployer.go b/cmd/juju/application/deployer/deployer.go index 7a2d8ae2bc1..55f6e925f8f 100644 --- a/cmd/juju/application/deployer/deployer.go +++ b/cmd/juju/application/deployer/deployer.go @@ -15,8 +15,6 @@ import ( jujuclock "github.com/juju/clock" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/api/client/application" commoncharm "github.com/juju/juju/api/common/charm" @@ -31,6 +29,8 @@ import ( "github.com/juju/juju/core/instance" "github.com/juju/juju/core/model" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/internal/storage" ) diff --git a/cmd/juju/application/deployer/deployer_test.go b/cmd/juju/application/deployer/deployer_test.go index 6b47edebeaa..d1e7a6e7bb6 100644 --- a/cmd/juju/application/deployer/deployer_test.go +++ b/cmd/juju/application/deployer/deployer_test.go @@ -11,8 +11,6 @@ import ( "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -27,6 +25,8 @@ import ( corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/model" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/testcharms" coretesting "github.com/juju/juju/testing" ) diff --git a/cmd/juju/application/deployer/interface.go b/cmd/juju/application/deployer/interface.go index 606fae21ba7..33ed7a4329a 100644 --- a/cmd/juju/application/deployer/interface.go +++ b/cmd/juju/application/deployer/interface.go @@ -8,8 +8,6 @@ import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery" "github.com/juju/cmd/v4" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/api" @@ -24,6 +22,8 @@ import ( "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/jujuclient" apiparams "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/application/deployer/lxdprofile.go b/cmd/juju/application/deployer/lxdprofile.go index b9020ef85a9..ab77301fd6d 100644 --- a/cmd/juju/application/deployer/lxdprofile.go +++ b/cmd/juju/application/deployer/lxdprofile.go @@ -4,10 +4,9 @@ package deployer import ( - "github.com/juju/juju/internal/charm" - apicharms "github.com/juju/juju/api/common/charms" "github.com/juju/juju/core/lxdprofile" + "github.com/juju/juju/internal/charm" ) // lxdCharmProfiler massages a charm.Charm into a LXDProfiler inside of the diff --git a/cmd/juju/application/deployer/resource.go b/cmd/juju/application/deployer/resource.go index 184fd7140ce..c8eaf86c38a 100644 --- a/cmd/juju/application/deployer/resource.go +++ b/cmd/juju/application/deployer/resource.go @@ -8,13 +8,13 @@ import ( "strconv" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/api/base" "github.com/juju/juju/api/client/application" "github.com/juju/juju/api/client/resources" resourcecmd "github.com/juju/juju/cmd/juju/resource" "github.com/juju/juju/cmd/modelcmd" + charmresource "github.com/juju/juju/internal/charm/resource" ) // DeployResourcesFunc is the function type of DeployResources. diff --git a/cmd/juju/application/diffbundle.go b/cmd/juju/application/diffbundle.go index 579ec2cd45f..eb804978c41 100644 --- a/cmd/juju/application/diffbundle.go +++ b/cmd/juju/application/diffbundle.go @@ -13,7 +13,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" "gopkg.in/yaml.v2" "github.com/juju/juju/api/base" @@ -32,6 +31,7 @@ import ( "github.com/juju/juju/core/constraints" "github.com/juju/juju/environs/config" bundlechanges "github.com/juju/juju/internal/bundle/changes" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/application/diffbundle_test.go b/cmd/juju/application/diffbundle_test.go index df66d590895..42fca8a95a5 100644 --- a/cmd/juju/application/diffbundle_test.go +++ b/cmd/juju/application/diffbundle_test.go @@ -14,7 +14,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -24,6 +23,7 @@ import ( "github.com/juju/juju/cmd/juju/application" "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/jujuclient" "github.com/juju/juju/jujuclient/jujuclienttesting" "github.com/juju/juju/rpc/params" diff --git a/cmd/juju/application/export_test.go b/cmd/juju/application/export_test.go index 6a5b42f7e3d..ff70ba8d428 100644 --- a/cmd/juju/application/export_test.go +++ b/cmd/juju/application/export_test.go @@ -5,7 +5,6 @@ package application import ( "github.com/juju/cmd/v4" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/api" "github.com/juju/juju/api/base" @@ -14,6 +13,7 @@ import ( "github.com/juju/juju/cmd/juju/application/store" "github.com/juju/juju/cmd/juju/application/utils" "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/jujuclient" ) diff --git a/cmd/juju/application/refresh.go b/cmd/juju/application/refresh.go index c26a7d67b22..43586732d9d 100644 --- a/cmd/juju/application/refresh.go +++ b/cmd/juju/application/refresh.go @@ -12,7 +12,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api" @@ -36,6 +35,7 @@ import ( corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" diff --git a/cmd/juju/application/refresh_test.go b/cmd/juju/application/refresh_test.go index e5469f93f57..1a699b93813 100644 --- a/cmd/juju/application/refresh_test.go +++ b/cmd/juju/application/refresh_test.go @@ -15,8 +15,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -41,6 +39,8 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/core/network" coreresouces "github.com/juju/juju/core/resources" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/storage" "github.com/juju/juju/jujuclient" diff --git a/cmd/juju/application/refresher/interface.go b/cmd/juju/application/refresher/interface.go index 6d1d6fdcb17..3b0fb2c5802 100644 --- a/cmd/juju/application/refresher/interface.go +++ b/cmd/juju/application/refresher/interface.go @@ -4,11 +4,10 @@ package refresher import ( - "github.com/juju/juju/internal/charm" - commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" ) // RefresherFactory contains a method to get a refresher. diff --git a/cmd/juju/application/refresher/refresher.go b/cmd/juju/application/refresher/refresher.go index 8020e034098..a959ba8e2ae 100644 --- a/cmd/juju/application/refresher/refresher.go +++ b/cmd/juju/application/refresher/refresher.go @@ -11,13 +11,13 @@ import ( jujuclock "github.com/juju/clock" "github.com/juju/collections/transform" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/cmd/juju/application/store" "github.com/juju/juju/cmd/juju/application/utils" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" ) // ErrExhausted reveals if a refresher was exhausted in it's task. If so, then diff --git a/cmd/juju/application/refresher/refresher_test.go b/cmd/juju/application/refresher/refresher_test.go index 1ed95aa32d1..c3bc33c2cb3 100644 --- a/cmd/juju/application/refresher/refresher_test.go +++ b/cmd/juju/application/refresher/refresher_test.go @@ -8,7 +8,6 @@ import ( "os" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -16,6 +15,7 @@ import ( commoncharm "github.com/juju/juju/api/common/charm" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" ) type refresherFactorySuite struct{} diff --git a/cmd/juju/application/store/charmadaptor.go b/cmd/juju/application/store/charmadaptor.go index 9479d5ce7f5..e85095a3ab7 100644 --- a/cmd/juju/application/store/charmadaptor.go +++ b/cmd/juju/application/store/charmadaptor.go @@ -8,11 +8,11 @@ import ( "net/url" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" apicharm "github.com/juju/juju/api/client/charms" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/charmhub/transport" ) diff --git a/cmd/juju/application/store/charmadaptor_test.go b/cmd/juju/application/store/charmadaptor_test.go index 6f33e676190..3f62af9dc4f 100644 --- a/cmd/juju/application/store/charmadaptor_test.go +++ b/cmd/juju/application/store/charmadaptor_test.go @@ -8,7 +8,6 @@ import ( "net/url" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -18,6 +17,7 @@ import ( "github.com/juju/juju/cmd/juju/application/store" "github.com/juju/juju/cmd/juju/application/store/mocks" "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" ) type resolveSuite struct { diff --git a/cmd/juju/application/store/interface.go b/cmd/juju/application/store/interface.go index 71a2db0ba94..2f845f17ede 100644 --- a/cmd/juju/application/store/interface.go +++ b/cmd/juju/application/store/interface.go @@ -4,10 +4,9 @@ package store import ( - "github.com/juju/juju/internal/charm" - apicharm "github.com/juju/juju/api/client/charms" commoncharm "github.com/juju/juju/api/common/charm" + "github.com/juju/juju/internal/charm" ) // CharmAdder defines a subset of the charm client needed to add a diff --git a/cmd/juju/application/store/resolve.go b/cmd/juju/application/store/resolve.go index caa79eaf3bd..3a2d196ba2e 100644 --- a/cmd/juju/application/store/resolve.go +++ b/cmd/juju/application/store/resolve.go @@ -5,6 +5,7 @@ package store import ( "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) diff --git a/cmd/juju/application/store/store.go b/cmd/juju/application/store/store.go index 0e609b75606..060c006a733 100644 --- a/cmd/juju/application/store/store.go +++ b/cmd/juju/application/store/store.go @@ -5,9 +5,9 @@ package store import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/application/store/store_test.go b/cmd/juju/application/store/store_test.go index b901e46704b..6359acef275 100644 --- a/cmd/juju/application/store/store_test.go +++ b/cmd/juju/application/store/store_test.go @@ -5,7 +5,6 @@ package store_test import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/cmd/juju/application/utils" "github.com/juju/juju/core/arch" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/application/utils/interface.go b/cmd/juju/application/utils/interface.go index 2130aed8afa..9edebc183f1 100644 --- a/cmd/juju/application/utils/interface.go +++ b/cmd/juju/application/utils/interface.go @@ -4,11 +4,10 @@ package utils import ( - charmresource "github.com/juju/juju/internal/charm/resource" - apicharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/api/common/charms" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/charmresource_mock.go github.com/juju/juju/cmd/juju/application/utils CharmClient diff --git a/cmd/juju/application/utils/origin.go b/cmd/juju/application/utils/origin.go index 4046f4f02ff..43cc3549059 100644 --- a/cmd/juju/application/utils/origin.go +++ b/cmd/juju/application/utils/origin.go @@ -5,13 +5,13 @@ package utils import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" commoncharm "github.com/juju/juju/api/common/charm" "github.com/juju/juju/core/arch" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/constraints" + "github.com/juju/juju/internal/charm" ) // MakeOrigin creates an origin from a schema, revision, channel and a platform. diff --git a/cmd/juju/application/utils/utils.go b/cmd/juju/application/utils/utils.go index 379a3fd4fe0..ac0e9d303ad 100644 --- a/cmd/juju/application/utils/utils.go +++ b/cmd/juju/application/utils/utils.go @@ -12,7 +12,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/mattn/go-isatty" goyaml "gopkg.in/yaml.v2" @@ -23,6 +22,7 @@ import ( corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/instance" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" internallogger "github.com/juju/juju/internal/logger" ) diff --git a/cmd/juju/application/utils/utils_test.go b/cmd/juju/application/utils/utils_test.go index c9f6fafb8a4..b328ee6380b 100644 --- a/cmd/juju/application/utils/utils_test.go +++ b/cmd/juju/application/utils/utils_test.go @@ -5,8 +5,6 @@ package utils_test import ( "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -18,6 +16,8 @@ import ( "github.com/juju/juju/cmd/juju/application/utils/mocks" "github.com/juju/juju/core/instance" "github.com/juju/juju/core/resources" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" ) type utilsSuite struct{} diff --git a/cmd/juju/charmhub/convert.go b/cmd/juju/charmhub/convert.go index 48842120c28..b12b1afacdd 100644 --- a/cmd/juju/charmhub/convert.go +++ b/cmd/juju/charmhub/convert.go @@ -12,11 +12,11 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/arch" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub/transport" ) diff --git a/cmd/juju/charmhub/download.go b/cmd/juju/charmhub/download.go index 11f41acb41d..3dbad6da569 100644 --- a/cmd/juju/charmhub/download.go +++ b/cmd/juju/charmhub/download.go @@ -16,13 +16,13 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/core/arch" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/output/progress" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/charmhub/transport" "github.com/juju/juju/version" diff --git a/cmd/juju/charmhub/info.go b/cmd/juju/charmhub/info.go index a759f3e4194..944387f1422 100644 --- a/cmd/juju/charmhub/info.go +++ b/cmd/juju/charmhub/info.go @@ -11,10 +11,10 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" corebase "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charmhub" ) diff --git a/cmd/juju/charmhub/infowriter.go b/cmd/juju/charmhub/infowriter.go index 4808a18faee..11a00d114a2 100644 --- a/cmd/juju/charmhub/infowriter.go +++ b/cmd/juju/charmhub/infowriter.go @@ -10,11 +10,11 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "gopkg.in/yaml.v2" corebase "github.com/juju/juju/core/base" "github.com/juju/juju/core/output" + "github.com/juju/juju/internal/charm" ) // Note: diff --git a/cmd/juju/charmhub/infowriter_test.go b/cmd/juju/charmhub/infowriter_test.go index 876fb9ebba3..bc35167d818 100644 --- a/cmd/juju/charmhub/infowriter_test.go +++ b/cmd/juju/charmhub/infowriter_test.go @@ -6,10 +6,11 @@ package charmhub import ( "bytes" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" ) type printInfoSuite struct { diff --git a/cmd/juju/commands/bootstrap.go b/cmd/juju/commands/bootstrap.go index 49f0f2dc584..c23c70a6810 100644 --- a/cmd/juju/commands/bootstrap.go +++ b/cmd/juju/commands/bootstrap.go @@ -17,7 +17,6 @@ import ( "github.com/juju/errors" "github.com/juju/featureflag" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/naturalsort" "github.com/juju/schema" @@ -49,6 +48,7 @@ import ( "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/envcontext" "github.com/juju/juju/environs/sync" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/docker" "github.com/juju/juju/internal/feature" _ "github.com/juju/juju/internal/provider/all" // Import all the providers for bootstrap. diff --git a/cmd/juju/commands/helptool.go b/cmd/juju/commands/helptool.go index 790fb3f2df3..4e64788e5f6 100644 --- a/cmd/juju/commands/helptool.go +++ b/cmd/juju/commands/helptool.go @@ -11,10 +11,10 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/core/network" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/worker/uniter/runner/jujuc" ) diff --git a/cmd/juju/crossmodel/find_test.go b/cmd/juju/crossmodel/find_test.go index 84846262652..aee309013fc 100644 --- a/cmd/juju/crossmodel/find_test.go +++ b/cmd/juju/crossmodel/find_test.go @@ -9,12 +9,12 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/cmd/modelcmd" jujucrossmodel "github.com/juju/juju/core/crossmodel" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/jujuclient" ) diff --git a/cmd/juju/crossmodel/list.go b/cmd/juju/crossmodel/list.go index a20becf9dff..ac64a5cd041 100644 --- a/cmd/juju/crossmodel/list.go +++ b/cmd/juju/crossmodel/list.go @@ -11,13 +11,13 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/api/client/applicationoffers" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/cmd/juju/common" "github.com/juju/juju/cmd/modelcmd" "github.com/juju/juju/core/crossmodel" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/jujuclient" ) diff --git a/cmd/juju/crossmodel/list_test.go b/cmd/juju/crossmodel/list_test.go index 371cba320e8..ceace7a7570 100644 --- a/cmd/juju/crossmodel/list_test.go +++ b/cmd/juju/crossmodel/list_test.go @@ -10,13 +10,13 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/cmd/modelcmd" model "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/relation" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/jujuclient" ) diff --git a/cmd/juju/crossmodel/remoteendpoints.go b/cmd/juju/crossmodel/remoteendpoints.go index 507220753af..c6f5c969eea 100644 --- a/cmd/juju/crossmodel/remoteendpoints.go +++ b/cmd/juju/crossmodel/remoteendpoints.go @@ -4,10 +4,9 @@ package crossmodel import ( - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/api/client/applicationoffers" "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/internal/charm" ) // RemoteEndpointsCommandBase is a base for various cross model commands. diff --git a/cmd/juju/crossmodel/show_test.go b/cmd/juju/crossmodel/show_test.go index e9449c2530f..374f0f07fc0 100644 --- a/cmd/juju/crossmodel/show_test.go +++ b/cmd/juju/crossmodel/show_test.go @@ -9,12 +9,12 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/cmd/modelcmd" jujucrossmodel "github.com/juju/juju/core/crossmodel" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/juju/osenv" "github.com/juju/juju/jujuclient" ) diff --git a/cmd/juju/payload/util_test.go b/cmd/juju/payload/util_test.go index ffcd26d8164..c80c7d24d89 100644 --- a/cmd/juju/payload/util_test.go +++ b/cmd/juju/payload/util_test.go @@ -6,9 +6,8 @@ package payload import ( "fmt" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" ) func NewPayload(name, application string, machine, unit int, labels ...string) payloads.FullPayloadInfo { diff --git a/cmd/juju/resource/charmresources.go b/cmd/juju/resource/charmresources.go index 01ab422ba84..0d56bd9113a 100644 --- a/cmd/juju/resource/charmresources.go +++ b/cmd/juju/resource/charmresources.go @@ -7,14 +7,14 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/api" "github.com/juju/juju/api/client/charms" apicharm "github.com/juju/juju/api/common/charm" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" ) // ResourceLister lists resources for the given charm ids. diff --git a/cmd/juju/resource/charmresources_test.go b/cmd/juju/resource/charmresources_test.go index cf92a6425ee..8fe5d60de25 100644 --- a/cmd/juju/resource/charmresources_test.go +++ b/cmd/juju/resource/charmresources_test.go @@ -6,8 +6,6 @@ package resource_test import ( "strings" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -15,6 +13,8 @@ import ( jujuresource "github.com/juju/juju/cmd/juju/resource" resourcecmd "github.com/juju/juju/cmd/juju/resource" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" ) var _ = gc.Suite(&CharmResourcesSuite{}) diff --git a/cmd/juju/resource/deploy.go b/cmd/juju/resource/deploy.go index bf5c817b472..390d13651d4 100644 --- a/cmd/juju/resource/deploy.go +++ b/cmd/juju/resource/deploy.go @@ -8,10 +8,10 @@ import ( "io" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" apiresources "github.com/juju/juju/api/client/resources" "github.com/juju/juju/cmd/modelcmd" + charmresource "github.com/juju/juju/internal/charm/resource" ) // DeployClient exposes the functionality of the resources API needed diff --git a/cmd/juju/resource/deploy_test.go b/cmd/juju/resource/deploy_test.go index 2b667452e6a..2cf6ea38817 100644 --- a/cmd/juju/resource/deploy_test.go +++ b/cmd/juju/resource/deploy_test.go @@ -12,13 +12,13 @@ import ( "strings" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" apiresources "github.com/juju/juju/api/client/resources" "github.com/juju/juju/cmd/modelcmd" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/docker" ) diff --git a/cmd/juju/resource/formatter.go b/cmd/juju/resource/formatter.go index ce0dacfc987..6e4a676f832 100644 --- a/cmd/juju/resource/formatter.go +++ b/cmd/juju/resource/formatter.go @@ -8,10 +8,10 @@ import ( "sort" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) type charmResourcesFormatter struct { diff --git a/cmd/juju/resource/formatter_test.go b/cmd/juju/resource/formatter_test.go index ee7fae21be2..09c086f1f4e 100644 --- a/cmd/juju/resource/formatter_test.go +++ b/cmd/juju/resource/formatter_test.go @@ -8,7 +8,6 @@ import ( "strings" "time" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -16,6 +15,7 @@ import ( resourcecmd "github.com/juju/juju/cmd/juju/resource" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) var _ = gc.Suite(&CharmFormatterSuite{}) diff --git a/cmd/juju/resource/list_test.go b/cmd/juju/resource/list_test.go index 73fa83852f0..99de9255c7d 100644 --- a/cmd/juju/resource/list_test.go +++ b/cmd/juju/resource/list_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -15,6 +14,7 @@ import ( resourcecmd "github.com/juju/juju/cmd/juju/resource" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) var _ = gc.Suite(&ShowApplicationSuite{}) diff --git a/cmd/juju/resource/output_tabular_test.go b/cmd/juju/resource/output_tabular_test.go index 41242db517b..8d5dbcc6dd7 100644 --- a/cmd/juju/resource/output_tabular_test.go +++ b/cmd/juju/resource/output_tabular_test.go @@ -7,13 +7,13 @@ import ( "bytes" "time" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" resourcecmd "github.com/juju/juju/cmd/juju/resource" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) var _ = gc.Suite(&CharmTabularSuite{}) diff --git a/cmd/juju/resource/resource.go b/cmd/juju/resource/resource.go index 3318ea27dad..fcee8e7ef08 100644 --- a/cmd/juju/resource/resource.go +++ b/cmd/juju/resource/resource.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/resource" ) diff --git a/cmd/juju/resource/stub_test.go b/cmd/juju/resource/stub_test.go index f6abadcf36a..28b9b53d435 100644 --- a/cmd/juju/resource/stub_test.go +++ b/cmd/juju/resource/stub_test.go @@ -8,11 +8,11 @@ import ( "io" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jujuresource "github.com/juju/juju/cmd/juju/resource" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) type stubCharmStore struct { diff --git a/cmd/juju/resource/upload.go b/cmd/juju/resource/upload.go index eec6b31c746..08e14ab2f9f 100644 --- a/cmd/juju/resource/upload.go +++ b/cmd/juju/resource/upload.go @@ -9,7 +9,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/api/client/resources" @@ -17,6 +16,7 @@ import ( "github.com/juju/juju/cmd/juju/block" "github.com/juju/juju/cmd/modelcmd" coreresources "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) // UploadClient has the API client methods needed by UploadCommand. diff --git a/cmd/juju/resource/upload_test.go b/cmd/juju/resource/upload_test.go index 99e450ef129..520d46ba367 100644 --- a/cmd/juju/resource/upload_test.go +++ b/cmd/juju/resource/upload_test.go @@ -7,7 +7,6 @@ import ( "bytes" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -15,6 +14,7 @@ import ( resourcecmd "github.com/juju/juju/cmd/juju/resource" "github.com/juju/juju/cmd/modelcmd" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/resource/util_test.go b/cmd/juju/resource/util_test.go index 123574e8ad0..602523faede 100644 --- a/cmd/juju/resource/util_test.go +++ b/cmd/juju/resource/util_test.go @@ -9,9 +9,10 @@ import ( jujucmd "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + + charmresource "github.com/juju/juju/internal/charm/resource" ) func charmRes(c *gc.C, name, suffix, description, content string) charmresource.Resource { diff --git a/cmd/juju/resource/validation.go b/cmd/juju/resource/validation.go index 44164449df6..3bb0e8db663 100644 --- a/cmd/juju/resource/validation.go +++ b/cmd/juju/resource/validation.go @@ -12,11 +12,11 @@ import ( "strings" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "gopkg.in/yaml.v2" "github.com/juju/juju/cmd/juju/application/utils" "github.com/juju/juju/cmd/modelcmd" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/docker" ) diff --git a/cmd/juju/ssh/debugcode_test.go b/cmd/juju/ssh/debugcode_test.go index 406aabf84bc..0d0a99ec82e 100644 --- a/cmd/juju/ssh/debugcode_test.go +++ b/cmd/juju/ssh/debugcode_test.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/juju/cmd/v4/cmdtesting" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -19,6 +18,7 @@ import ( "github.com/juju/juju/api/common/charms" "github.com/juju/juju/cmd/juju/ssh/mocks" "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/internal/charm" ) var _ = gc.Suite(&DebugCodeSuite{}) diff --git a/cmd/juju/ssh/debughooks.go b/cmd/juju/ssh/debughooks.go index 46ab4d3e0f0..f0cec82fb0c 100644 --- a/cmd/juju/ssh/debughooks.go +++ b/cmd/juju/ssh/debughooks.go @@ -11,8 +11,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/retry" @@ -21,6 +19,8 @@ import ( jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/cmd/modelcmd" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/network/ssh" unitdebug "github.com/juju/juju/internal/worker/uniter/runner/debug" ) diff --git a/cmd/juju/ssh/debughooks_test.go b/cmd/juju/ssh/debughooks_test.go index a23aa2b45fc..4095323a468 100644 --- a/cmd/juju/ssh/debughooks_test.go +++ b/cmd/juju/ssh/debughooks_test.go @@ -12,7 +12,6 @@ import ( "github.com/juju/clock" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/retry" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -23,6 +22,7 @@ import ( "github.com/juju/juju/api/common/charms" "github.com/juju/juju/cmd/juju/ssh/mocks" "github.com/juju/juju/cmd/modelcmd" + "github.com/juju/juju/internal/charm" jujussh "github.com/juju/juju/internal/network/ssh" ) diff --git a/cmd/juju/ssh/interface.go b/cmd/juju/ssh/interface.go index b49a6147549..6b04200ab41 100644 --- a/cmd/juju/ssh/interface.go +++ b/cmd/juju/ssh/interface.go @@ -4,7 +4,6 @@ package ssh import ( - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/client/application" @@ -14,6 +13,7 @@ import ( jujucloud "github.com/juju/juju/cloud" "github.com/juju/juju/controller" "github.com/juju/juju/environs/cloudspec" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/ssh/ssh_container_test.go b/cmd/juju/ssh/ssh_container_test.go index c4cf77f0ee6..d54bf21dfd2 100644 --- a/cmd/juju/ssh/ssh_container_test.go +++ b/cmd/juju/ssh/ssh_container_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -24,6 +23,7 @@ import ( "github.com/juju/juju/cmd/juju/ssh/mocks" "github.com/juju/juju/controller" "github.com/juju/juju/environs/cloudspec" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/testing" ) diff --git a/cmd/juju/status/formatter.go b/cmd/juju/status/formatter.go index b4e32812aa1..0bfdd219bee 100644 --- a/cmd/juju/status/formatter.go +++ b/cmd/juju/status/formatter.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/collections/set" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/naturalsort" @@ -17,6 +16,7 @@ import ( corebase "github.com/juju/juju/core/base" coremodel "github.com/juju/juju/core/model" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/cmd/juju/status/output_tabular.go b/cmd/juju/status/output_tabular.go index 4ea3860ff22..336078e621e 100644 --- a/cmd/juju/status/output_tabular.go +++ b/cmd/juju/status/output_tabular.go @@ -14,8 +14,6 @@ import ( "github.com/docker/distribution/reference" "github.com/juju/ansiterm" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/naturalsort" "github.com/juju/version/v2" @@ -27,6 +25,8 @@ import ( "github.com/juju/juju/core/output" "github.com/juju/juju/core/relation" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" jujuversion "github.com/juju/juju/version" ) diff --git a/cmd/juju/status/status_internal_test.go b/cmd/juju/status/status_internal_test.go index bb5de24edc0..ee76ebec5bc 100644 --- a/cmd/juju/status/status_internal_test.go +++ b/cmd/juju/status/status_internal_test.go @@ -17,7 +17,6 @@ import ( "github.com/juju/clock" "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -33,6 +32,7 @@ import ( coremodel "github.com/juju/juju/core/model" "github.com/juju/juju/core/network" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/juju/osenv" "github.com/juju/juju/jujuclient" "github.com/juju/juju/rpc/params" diff --git a/cmd/jujud-controller/agent/bootstrap_test.go b/cmd/jujud-controller/agent/bootstrap_test.go index c1dd79388cf..13f68b54062 100644 --- a/cmd/jujud-controller/agent/bootstrap_test.go +++ b/cmd/jujud-controller/agent/bootstrap_test.go @@ -16,7 +16,6 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" mgotesting "github.com/juju/mgo/v3/testing" "github.com/juju/names/v5" @@ -47,6 +46,7 @@ import ( "github.com/juju/juju/environs/storage" envtesting "github.com/juju/juju/environs/testing" envtools "github.com/juju/juju/environs/tools" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig/instancecfg" "github.com/juju/juju/internal/database" "github.com/juju/juju/internal/mongo" diff --git a/core/assumes/featureset.go b/core/assumes/featureset.go index 9e0d99e5c1e..ed9a40b71e8 100644 --- a/core/assumes/featureset.go +++ b/core/assumes/featureset.go @@ -5,8 +5,9 @@ package assumes import ( "github.com/juju/collections/set" - chassumes "github.com/juju/juju/internal/charm/assumes" "github.com/juju/version/v2" + + chassumes "github.com/juju/juju/internal/charm/assumes" ) // Feature identifies a particular piece of functionality provided by a Juju diff --git a/core/assumes/sat_checker.go b/core/assumes/sat_checker.go index 458a24bb3ec..41d00b0a672 100644 --- a/core/assumes/sat_checker.go +++ b/core/assumes/sat_checker.go @@ -5,6 +5,7 @@ package assumes import ( "github.com/juju/errors" + chassumes "github.com/juju/juju/internal/charm/assumes" ) diff --git a/core/assumes/sat_checker_test.go b/core/assumes/sat_checker_test.go index 474f162a6ae..aae459a8515 100644 --- a/core/assumes/sat_checker_test.go +++ b/core/assumes/sat_checker_test.go @@ -6,12 +6,13 @@ package assumes import ( "strings" - chassumes "github.com/juju/juju/internal/charm/assumes" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" gc "gopkg.in/check.v1" "gopkg.in/yaml.v3" + + chassumes "github.com/juju/juju/internal/charm/assumes" ) type SatCheckerSuite struct { diff --git a/core/base/base.go b/core/base/base.go index d81068aa0aa..db74127f799 100644 --- a/core/base/base.go +++ b/core/base/base.go @@ -9,6 +9,7 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) diff --git a/core/base/base_test.go b/core/base/base_test.go index 8febd3584e6..1fd9d35f96d 100644 --- a/core/base/base_test.go +++ b/core/base/base_test.go @@ -4,10 +4,11 @@ package base import ( - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" ) type BaseSuite struct { diff --git a/core/charm/charmpath.go b/core/charm/charmpath.go index f34a6715e27..d52e3aad6f9 100644 --- a/core/charm/charmpath.go +++ b/core/charm/charmpath.go @@ -10,9 +10,9 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" ) // NewCharmAtPath returns the charm represented by this path, diff --git a/core/charm/charmpath_test.go b/core/charm/charmpath_test.go index aafd43c974b..135259028bc 100644 --- a/core/charm/charmpath_test.go +++ b/core/charm/charmpath_test.go @@ -7,11 +7,11 @@ import ( "os" "path/filepath" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/testcharms" ) diff --git a/core/charm/computedbase.go b/core/charm/computedbase.go index b60b46c4d24..c790cd63b15 100644 --- a/core/charm/computedbase.go +++ b/core/charm/computedbase.go @@ -10,9 +10,9 @@ import ( "github.com/juju/collections/set" "github.com/juju/collections/transform" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" ) // BaseForCharm takes a requested base and a list of bases supported by a diff --git a/core/charm/computedbase_test.go b/core/charm/computedbase_test.go index 6e6e3e27778..c3146144449 100644 --- a/core/charm/computedbase_test.go +++ b/core/charm/computedbase_test.go @@ -5,13 +5,13 @@ package charm import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" ) type computedBaseSuite struct { diff --git a/core/charm/format_test.go b/core/charm/format_test.go index aea3772f0f7..ef79241bbc4 100644 --- a/core/charm/format_test.go +++ b/core/charm/format_test.go @@ -4,10 +4,11 @@ package charm import ( - "github.com/juju/juju/internal/charm" "github.com/juju/testing" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" ) type formatSuite struct { diff --git a/core/charm/origin.go b/core/charm/origin.go index d1d5dfd0610..d906899d56c 100644 --- a/core/charm/origin.go +++ b/core/charm/origin.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) diff --git a/core/crossmodel/interface.go b/core/crossmodel/interface.go index 34c5dbe89dd..60bdf9c3dae 100644 --- a/core/crossmodel/interface.go +++ b/core/crossmodel/interface.go @@ -4,9 +4,9 @@ package crossmodel import ( - "github.com/juju/juju/internal/charm" "gopkg.in/macaroon.v2" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/core/crossmodel/params.go b/core/crossmodel/params.go index 7e5cddc31dd..56b0dc217bf 100644 --- a/core/crossmodel/params.go +++ b/core/crossmodel/params.go @@ -6,10 +6,9 @@ package crossmodel import ( "time" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/core/permission" "github.com/juju/juju/core/relation" + "github.com/juju/juju/internal/charm" ) // ApplicationOfferDetails represents the details about an diff --git a/core/payloads/filter_test.go b/core/payloads/filter_test.go index b49ec30c81a..af19eec3448 100644 --- a/core/payloads/filter_test.go +++ b/core/payloads/filter_test.go @@ -4,12 +4,12 @@ package payloads_test import ( - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" ) var _ = gc.Suite(&filterSuite{}) diff --git a/core/payloads/payload.go b/core/payloads/payload.go index 5d9a0fb89d1..c2d01a1242a 100644 --- a/core/payloads/payload.go +++ b/core/payloads/payload.go @@ -5,6 +5,7 @@ package payloads import ( "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) diff --git a/core/payloads/payload_test.go b/core/payloads/payload_test.go index 2d088083017..28f9c49f59f 100644 --- a/core/payloads/payload_test.go +++ b/core/payloads/payload_test.go @@ -4,12 +4,12 @@ package payloads_test import ( - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" ) var _ = gc.Suite(&payloadSuite{}) diff --git a/core/resources/application.go b/core/resources/application.go index 3706b3d2e3c..a2b137e5f3a 100644 --- a/core/resources/application.go +++ b/core/resources/application.go @@ -5,8 +5,9 @@ package resources import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" + + "github.com/juju/juju/internal/charm/resource" ) // ApplicationResources contains the list of resources for the application and all its diff --git a/core/resources/application_test.go b/core/resources/application_test.go index 52de5f3a891..e748e78ef3b 100644 --- a/core/resources/application_test.go +++ b/core/resources/application_test.go @@ -4,13 +4,13 @@ package resources_test import ( - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/resources" resourcetesting "github.com/juju/juju/core/resources/testing" + charmresource "github.com/juju/juju/internal/charm/resource" ) type ServiceResourcesSuite struct { diff --git a/core/resources/content.go b/core/resources/content.go index 91b93c5803c..0d93bbdfe22 100644 --- a/core/resources/content.go +++ b/core/resources/content.go @@ -10,8 +10,9 @@ import ( "os" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/utils/v4" + + charmresource "github.com/juju/juju/internal/charm/resource" ) // Content holds a reader for the content of a resource along diff --git a/core/resources/resource.go b/core/resources/resource.go index 07c411a0e79..8af6d66d095 100644 --- a/core/resources/resource.go +++ b/core/resources/resource.go @@ -8,6 +8,7 @@ import ( "time" "github.com/juju/errors" + "github.com/juju/juju/internal/charm/resource" ) diff --git a/core/resources/resource_test.go b/core/resources/resource_test.go index 0fe1a707d1c..f55c5efdb37 100644 --- a/core/resources/resource_test.go +++ b/core/resources/resource_test.go @@ -7,12 +7,12 @@ import ( "time" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) type ResourceSuite struct { diff --git a/core/resources/serialization.go b/core/resources/serialization.go index 7b1221b3d29..1512b86dc48 100644 --- a/core/resources/serialization.go +++ b/core/resources/serialization.go @@ -5,6 +5,7 @@ package resources import ( "github.com/juju/errors" + "github.com/juju/juju/internal/charm/resource" ) diff --git a/core/resources/serialization_test.go b/core/resources/serialization_test.go index 03a269e7988..4eb71cd8da7 100644 --- a/core/resources/serialization_test.go +++ b/core/resources/serialization_test.go @@ -7,12 +7,12 @@ import ( "strings" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) type SerializationSuite struct { diff --git a/core/resources/testing/resource.go b/core/resources/testing/resource.go index d7d0d293859..5df79558619 100644 --- a/core/resources/testing/resource.go +++ b/core/resources/testing/resource.go @@ -8,13 +8,13 @@ import ( "strings" "time" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/testing/filetesting" gc "gopkg.in/check.v1" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" ) type newCharmResourceFunc func(c *gc.C, name, content string) charmresource.Resource diff --git a/core/resources/util_test.go b/core/resources/util_test.go index 6cfa21ab5b9..483af3226c9 100644 --- a/core/resources/util_test.go +++ b/core/resources/util_test.go @@ -6,9 +6,10 @@ package resources_test import ( "strings" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + + charmresource "github.com/juju/juju/internal/charm/resource" ) func newFingerprint(c *gc.C, data string) charmresource.Fingerprint { diff --git a/core/settings/settings.go b/core/settings/settings.go index 4ceccb6ea1b..f9ee4ef1838 100644 --- a/core/settings/settings.go +++ b/core/settings/settings.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/juju/errors" + "github.com/juju/juju/internal/charm" ) diff --git a/domain/application/service/params.go b/domain/application/service/params.go index 2a6e069d0d5..c4d63e3fa16 100644 --- a/domain/application/service/params.go +++ b/domain/application/service/params.go @@ -5,7 +5,6 @@ package service import ( "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/storage" ) diff --git a/domain/application/service/service.go b/domain/application/service/service.go index 372b818602a..7fe6e9e8b65 100644 --- a/domain/application/service/service.go +++ b/domain/application/service/service.go @@ -8,13 +8,13 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/logger" coremodel "github.com/juju/juju/core/model" "github.com/juju/juju/domain/application" applicationerrors "github.com/juju/juju/domain/application/errors" domainstorage "github.com/juju/juju/domain/storage" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" ) diff --git a/domain/application/service/service_test.go b/domain/application/service/service_test.go index 04305ccf728..0b3874a316c 100644 --- a/domain/application/service/service_test.go +++ b/domain/application/service/service_test.go @@ -7,7 +7,6 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/domain/application" domainstorage "github.com/juju/juju/domain/storage" storageerrors "github.com/juju/juju/domain/storage/errors" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/storage/provider" diff --git a/domain/storage/defaults.go b/domain/storage/defaults.go index 0815a420586..f3fdb657c9c 100644 --- a/domain/storage/defaults.go +++ b/domain/storage/defaults.go @@ -7,11 +7,11 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" coremodel "github.com/juju/juju/core/model" storageerrors "github.com/juju/juju/domain/storage/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/storage/provider" ) diff --git a/domain/storage/defaults_test.go b/domain/storage/defaults_test.go index 724e3a58cb4..9b67ddb66c2 100644 --- a/domain/storage/defaults_test.go +++ b/domain/storage/defaults_test.go @@ -4,13 +4,13 @@ package storage_test import ( - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" coremodel "github.com/juju/juju/core/model" domainstorage "github.com/juju/juju/domain/storage" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" ) diff --git a/domain/storage/validation.go b/domain/storage/validation.go index 175b856881b..fc08abe51d2 100644 --- a/domain/storage/validation.go +++ b/domain/storage/validation.go @@ -9,10 +9,10 @@ import ( "github.com/dustin/go-humanize" "github.com/juju/collections/transform" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" coremodel "github.com/juju/juju/core/model" storageerrors "github.com/juju/juju/domain/storage/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" ) diff --git a/domain/storage/validation_test.go b/domain/storage/validation_test.go index bad0dc4279b..768a67f4e62 100644 --- a/domain/storage/validation_test.go +++ b/domain/storage/validation_test.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -16,6 +15,7 @@ import ( coremodel "github.com/juju/juju/core/model" domainstorage "github.com/juju/juju/domain/storage" storageerrors "github.com/juju/juju/domain/storage/errors" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/storage/provider" ) diff --git a/environs/bootstrap/bootstrap.go b/environs/bootstrap/bootstrap.go index bd8a0e27562..ba10eb724bd 100644 --- a/environs/bootstrap/bootstrap.go +++ b/environs/bootstrap/bootstrap.go @@ -12,7 +12,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/utils/v4" "github.com/juju/utils/v4/ssh" @@ -33,6 +32,7 @@ import ( "github.com/juju/juju/environs/storage" "github.com/juju/juju/environs/sync" "github.com/juju/juju/environs/tools" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig/instancecfg" "github.com/juju/juju/internal/cloudconfig/podcfg" internallogger "github.com/juju/juju/internal/logger" diff --git a/environs/bootstrap/bootstrap_test.go b/environs/bootstrap/bootstrap_test.go index 310e8115275..33de285d753 100644 --- a/environs/bootstrap/bootstrap_test.go +++ b/environs/bootstrap/bootstrap_test.go @@ -15,7 +15,6 @@ import ( "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -42,6 +41,7 @@ import ( "github.com/juju/juju/environs/sync" envtesting "github.com/juju/juju/environs/testing" envtools "github.com/juju/juju/environs/tools" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig/instancecfg" "github.com/juju/juju/internal/cloudconfig/podcfg" _ "github.com/juju/juju/internal/provider/dummy" diff --git a/internal/bootstrap/deployer.go b/internal/bootstrap/deployer.go index e9bea040abf..e36ffa58ac1 100644 --- a/internal/bootstrap/deployer.go +++ b/internal/bootstrap/deployer.go @@ -10,7 +10,6 @@ import ( "path/filepath" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/schema" "gopkg.in/juju/environschema.v1" @@ -32,6 +31,7 @@ import ( applicationservice "github.com/juju/juju/domain/application/service" "github.com/juju/juju/environs/bootstrap" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/state" ) diff --git a/internal/bootstrap/deployer_test.go b/internal/bootstrap/deployer_test.go index 98d21c7fb70..5eba8ac3544 100644 --- a/internal/bootstrap/deployer_test.go +++ b/internal/bootstrap/deployer_test.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/schema" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -27,6 +26,7 @@ import ( "github.com/juju/juju/core/objectstore" applicationservice "github.com/juju/juju/domain/application/service" "github.com/juju/juju/environs/bootstrap" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/state" ) diff --git a/internal/bootstrap/package_test.go b/internal/bootstrap/package_test.go index 89ad5193b71..bc09cbeea2f 100644 --- a/internal/bootstrap/package_test.go +++ b/internal/bootstrap/package_test.go @@ -6,7 +6,6 @@ package bootstrap import ( "testing" - "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" @@ -15,6 +14,7 @@ import ( corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/uuid" diff --git a/internal/bundle/changes/changes.go b/internal/bundle/changes/changes.go index b9c90e9b939..f692d055f75 100644 --- a/internal/bundle/changes/changes.go +++ b/internal/bundle/changes/changes.go @@ -12,10 +12,10 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" corebase "github.com/juju/juju/core/base" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" ) // ArchConstraint defines an architecture constraint. This is used to diff --git a/internal/bundle/changes/changes_test.go b/internal/bundle/changes/changes_test.go index 0aa6c4c6fe3..39863d69830 100644 --- a/internal/bundle/changes/changes_test.go +++ b/internal/bundle/changes/changes_test.go @@ -13,7 +13,6 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -21,6 +20,7 @@ import ( corebase "github.com/juju/juju/core/base" bundlechanges "github.com/juju/juju/internal/bundle/changes" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" ) diff --git a/internal/bundle/changes/diff.go b/internal/bundle/changes/diff.go index f374046527f..8a8e7b178d5 100644 --- a/internal/bundle/changes/diff.go +++ b/internal/bundle/changes/diff.go @@ -10,9 +10,9 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" ) // DiffSide represents one side of a bundle-model diff. diff --git a/internal/bundle/changes/diff_test.go b/internal/bundle/changes/diff_test.go index 961fb8c6bd5..1e2fe65b929 100644 --- a/internal/bundle/changes/diff_test.go +++ b/internal/bundle/changes/diff_test.go @@ -6,7 +6,6 @@ package bundlechanges_test import ( "strings" - "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/kr/pretty" @@ -15,6 +14,7 @@ import ( corebase "github.com/juju/juju/core/base" "github.com/juju/juju/core/logger" bundlechanges "github.com/juju/juju/internal/bundle/changes" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" ) diff --git a/internal/bundle/changes/handlers.go b/internal/bundle/changes/handlers.go index 3ed6f12a35f..f0b6656d357 100644 --- a/internal/bundle/changes/handlers.go +++ b/internal/bundle/changes/handlers.go @@ -10,12 +10,12 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/naturalsort" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" ) const Kubernetes = "kubernetes" diff --git a/internal/bundle/changes/handlers_test.go b/internal/bundle/changes/handlers_test.go index 85616ec1824..7abb2f78f25 100644 --- a/internal/bundle/changes/handlers_test.go +++ b/internal/bundle/changes/handlers_test.go @@ -4,12 +4,12 @@ package bundlechanges import ( - "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" corebase "github.com/juju/juju/core/base" + "github.com/juju/juju/internal/charm" ) type resolverSuite struct { diff --git a/internal/bundle/changes/model.go b/internal/bundle/changes/model.go index 871bf94d041..585584512b7 100644 --- a/internal/bundle/changes/model.go +++ b/internal/bundle/changes/model.go @@ -9,13 +9,13 @@ import ( "strconv" "github.com/juju/collections/set" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/naturalsort" "github.com/kr/pretty" corebase "github.com/juju/juju/core/base" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" ) // Model represents the existing deployment if any. diff --git a/internal/bundle/changes/model_test.go b/internal/bundle/changes/model_test.go index 6ea93d6ca03..1abe57fd2dd 100644 --- a/internal/bundle/changes/model_test.go +++ b/internal/bundle/changes/model_test.go @@ -6,13 +6,13 @@ package bundlechanges import ( "bytes" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/naturalsort" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" ) diff --git a/internal/charm/assumes/parser.go b/internal/charm/assumes/parser.go index 465e7f609ce..b0520cc99a3 100644 --- a/internal/charm/assumes/parser.go +++ b/internal/charm/assumes/parser.go @@ -34,10 +34,10 @@ type ExpressionTree struct { // // For example: // assumes: -// - foo -// - bar >= 1.42 -// - any-of: ... (nested expr) -// - all-of: ... (nested expr) +// - foo +// - bar >= 1.42 +// - any-of: ... (nested expr) +// - all-of: ... (nested expr) func parseAssumesExpressionTree(rootExprList []interface{}) (Expression, error) { var ( rootExpr = CompositeExpression{ @@ -98,12 +98,12 @@ func parseAssumesExpr(exprDecl interface{}) (Expression, error) { // // The EBNF grammar for a composite expression is: // -// composite-expr-decl: ("any-of"|"all-of") expr-decl-list +// composite-expr-decl: ("any-of"|"all-of") expr-decl-list // -// expr-decl-list: expr-decl+ +// expr-decl-list: expr-decl+ // -// expr-decl: feature-expr-decl | -// composite-expr-decl +// expr-decl: feature-expr-decl | +// composite-expr-decl // // The function expects a map with either a "any-of" or "all-of" key and // a value that is a slice of sub-expressions. @@ -145,13 +145,12 @@ func parseCompositeExpr(exprDecl map[string]interface{}) (CompositeExpression, e // // The EBNF grammar for feature expressions is: // -// feature-expr-decl: feature-ident | -// feature-ident version-constraint version-number -// -// version-constraint: ">=" | "<" -// feature-ident: [a-z][a-z0-9-]*[a-z0-9]+ -// version-number: \d+ (‘.’ \d+ (‘.’ \d+)?)? +// feature-expr-decl: feature-ident | +// feature-ident version-constraint version-number // +// version-constraint: ">=" | "<" +// feature-ident: [a-z][a-z0-9-]*[a-z0-9]+ +// version-number: \d+ (‘.’ \d+ (‘.’ \d+)?)? func parseFeatureExpr(exprDecl string) (FeatureExpression, error) { exprDecl = strings.TrimSpace(exprDecl) diff --git a/internal/charm/channel.go b/internal/charm/channel.go index 74dc51aa1fd..0fe781ac150 100644 --- a/internal/charm/channel.go +++ b/internal/charm/channel.go @@ -41,18 +41,17 @@ func isRisk(potential string) bool { // // A channel consists of, and is subdivided by, tracks, risk-levels and // branches: -// - Tracks enable snap developers to publish multiple supported releases of -// their application under the same snap name. -// - Risk-levels represent a progressive potential trade-off between stability -// and new features. -// - Branches are _optional_ and hold temporary releases intended to help with -// bug-fixing. +// - Tracks enable snap developers to publish multiple supported releases of +// their application under the same snap name. +// - Risk-levels represent a progressive potential trade-off between stability +// and new features. +// - Branches are _optional_ and hold temporary releases intended to help with +// bug-fixing. // // The complete channel name can be structured as three distinct parts separated // by slashes: // -// // -// +// // type Channel struct { Track string `json:"track,omitempty"` Risk Risk `json:"risk,omitempty"` diff --git a/internal/charm/downloader/downloader.go b/internal/charm/downloader/downloader.go index e1fe1baafcf..b4a41a3b03a 100644 --- a/internal/charm/downloader/downloader.go +++ b/internal/charm/downloader/downloader.go @@ -11,13 +11,13 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/utils/v4" "github.com/juju/juju/core/arch" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/lxdprofile" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/version" ) diff --git a/internal/charm/downloader/downloader_test.go b/internal/charm/downloader/downloader_test.go index 50dce9927d2..e988a84c47d 100644 --- a/internal/charm/downloader/downloader_test.go +++ b/internal/charm/downloader/downloader_test.go @@ -11,7 +11,6 @@ import ( "os" "path/filepath" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -19,6 +18,7 @@ import ( gc "gopkg.in/check.v1" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/downloader" "github.com/juju/juju/internal/charm/downloader/mocks" loggertesting "github.com/juju/juju/internal/logger/testing" diff --git a/internal/charm/jujuignore.go b/internal/charm/jujuignore.go index 009bc2ba1be..e40b4667f08 100644 --- a/internal/charm/jujuignore.go +++ b/internal/charm/jujuignore.go @@ -10,7 +10,6 @@ import ( "unicode" "github.com/juju/errors" - "gopkg.in/gobwas/glob.v0" ) diff --git a/internal/charm/overlay.go b/internal/charm/overlay.go index e5db5b32427..79216a01bd9 100644 --- a/internal/charm/overlay.go +++ b/internal/charm/overlay.go @@ -30,18 +30,18 @@ import ( // To clarify how this method works let's consider a bundle created via the // yaml blob below: // -// applications: -// apache2: -// charm: cs:apache2-26 -// offers: -// my-offer: -// endpoints: -// - apache-website -// - website-cache -// my-other-offer: -// endpoints: -// - apache-website -// series: bionic +// applications: +// apache2: +// charm: cs:apache2-26 +// offers: +// my-offer: +// endpoints: +// - apache-website +// - website-cache +// my-other-offer: +// endpoints: +// - apache-website +// series: bionic // // The "offers" and "endpoints" attributes are overlay-specific fields. If we // were to run this method and then marshal the results back to yaml we would @@ -49,23 +49,23 @@ import ( // // The base bundle: // -// applications: -// apache2: -// charm: cs:apache2-26 -// series: bionic +// applications: +// apache2: +// charm: cs:apache2-26 +// series: bionic // // The overlay-specific bundle: // -// applications: -// apache2: -// offers: -// my-offer: -// endpoints: -// - apache-website -// - website-cache -// my-other-offer: -// endpoints: -// - apache-website +// applications: +// apache2: +// offers: +// my-offer: +// endpoints: +// - apache-website +// - website-cache +// my-other-offer: +// endpoints: +// - apache-website // // The two bundles returned by this method are copies of the original bundle // data and can thus be safely manipulated by the caller. @@ -373,29 +373,29 @@ func isZero(v reflect.Value) bool { // // When merging an overlay into a base bundle the following rules apply for the // BundleData struct fields: -// - if an overlay specifies a bundle-level series, it overrides the base bundle -// series. -// - overlay-defined relations are appended to the base bundle relations -// - overlay-defined machines overwrite the base bundle machines. -// - if an overlay defines an application that is not present in the base bundle, -// it will get appended to the application list. -// - if an overlay defines an empty application or saas value, it will be removed -// from the base bundle together with any associated relations. For example, to -// remove an application named "mysql" the following overlay snippet can be -// provided: +// - if an overlay specifies a bundle-level series, it overrides the base bundle +// series. +// - overlay-defined relations are appended to the base bundle relations +// - overlay-defined machines overwrite the base bundle machines. +// - if an overlay defines an application that is not present in the base bundle, +// it will get appended to the application list. +// - if an overlay defines an empty application or saas value, it will be removed +// from the base bundle together with any associated relations. For example, to +// remove an application named "mysql" the following overlay snippet can be +// provided: // applications: -// mysql: +// mysql: // -// - if an overlay defines an application that is also present in the base bundle -// the two application specs are merged together (see following rules) +// - if an overlay defines an application that is also present in the base bundle +// the two application specs are merged together (see following rules) // // ApplicationSpec merge rules: -// - if the overlay defines a value for a scalar or slice field, it will overwrite -// the value from the base spec (e.g. trust, series etc). -// - if the overlay specifies a nil/empty value for a map field, then the map -// field of the base spec will be cleared. -// - if the overlay specifies a non-empty value for a map field, its key/value -// tuples are iterated and: +// - if the overlay defines a value for a scalar or slice field, it will overwrite +// the value from the base spec (e.g. trust, series etc). +// - if the overlay specifies a nil/empty value for a map field, then the map +// field of the base spec will be cleared. +// - if the overlay specifies a non-empty value for a map field, its key/value +// tuples are iterated and: // - if the value is nil/zero and the value is non-scalar, it is deleted from // the base spec. // - otherwise, the key/value is inserted into the base spec overwriting any @@ -621,12 +621,12 @@ func removeRelations(data [][]string, appName string) [][]string { // mergeStructs iterates the fields of srcStruct and merges them into the // equivalent fields of dstStruct using the following rules: // -// - if src defines a value for a scalar or slice field, it will overwrite -// the value from the dst (e.g. trust, series etc). -// - if the src specifies a nil/empty value for a map field, then the map -// field of dst will be cleared. -// - if the src specifies a non-empty value for a map field, its key/value -// tuples are iterated and: +// - if src defines a value for a scalar or slice field, it will overwrite +// the value from the dst (e.g. trust, series etc). +// - if the src specifies a nil/empty value for a map field, then the map +// field of dst will be cleared. +// - if the src specifies a non-empty value for a map field, its key/value +// tuples are iterated and: // - if the value is nil/zero and non-scalar, it is deleted from the dst map. // - otherwise, the key/value is inserted into the dst map overwriting any // existing entries. diff --git a/internal/charm/repository/charmhub.go b/internal/charm/repository/charmhub.go index d2cfcc1fe0e..8a23e30659a 100644 --- a/internal/charm/repository/charmhub.go +++ b/internal/charm/repository/charmhub.go @@ -11,12 +11,12 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/charmhub/transport" ) diff --git a/internal/charm/repository/charmhub_test.go b/internal/charm/repository/charmhub_test.go index 4e204f277f7..92435c6bc4a 100644 --- a/internal/charm/repository/charmhub_test.go +++ b/internal/charm/repository/charmhub_test.go @@ -11,8 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4/hash" @@ -21,7 +19,9 @@ import ( "github.com/juju/juju/core/arch" corecharm "github.com/juju/juju/core/charm" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/repository/mocks" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/charmhub/transport" loggertesting "github.com/juju/juju/internal/logger/testing" diff --git a/internal/charmhub/client.go b/internal/charmhub/client.go index 755b9fa03c7..fa80cd21d0e 100644 --- a/internal/charmhub/client.go +++ b/internal/charmhub/client.go @@ -25,10 +25,10 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" charmmetrics "github.com/juju/juju/core/charm/metrics" corelogger "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" charmhubpath "github.com/juju/juju/internal/charmhub/path" "github.com/juju/juju/internal/charmhub/transport" ) diff --git a/internal/charmhub/download.go b/internal/charmhub/download.go index ca1a1ddce6a..d2fc805bcdb 100644 --- a/internal/charmhub/download.go +++ b/internal/charmhub/download.go @@ -12,10 +12,10 @@ import ( "runtime/pprof" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" corelogger "github.com/juju/juju/core/logger" "github.com/juju/juju/core/trace" + "github.com/juju/juju/internal/charm" ) // FileSystem defines a file system for modifying files on a users system. diff --git a/internal/cloudconfig/instancecfg/instancecfg.go b/internal/cloudconfig/instancecfg/instancecfg.go index 7e03cf9d271..506d685cce7 100644 --- a/internal/cloudconfig/instancecfg/instancecfg.go +++ b/internal/cloudconfig/instancecfg/instancecfg.go @@ -16,7 +16,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/proxy" "github.com/juju/utils/v4/shell" @@ -37,6 +36,7 @@ import ( "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/imagemetadata" "github.com/juju/juju/environs/tags" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/internal/service" "github.com/juju/juju/internal/service/common" diff --git a/internal/cloudconfig/podcfg/image.go b/internal/cloudconfig/podcfg/image.go index 145fc709cfa..fecd4d0956b 100644 --- a/internal/cloudconfig/podcfg/image.go +++ b/internal/cloudconfig/podcfg/image.go @@ -9,10 +9,10 @@ import ( "github.com/docker/distribution/reference" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/version/v2" "github.com/juju/juju/controller" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/docker" ) diff --git a/internal/cloudconfig/podcfg/image_test.go b/internal/cloudconfig/podcfg/image_test.go index 9b88758ef79..ba2eba54f0b 100644 --- a/internal/cloudconfig/podcfg/image_test.go +++ b/internal/cloudconfig/podcfg/image_test.go @@ -4,12 +4,12 @@ package podcfg_test import ( - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" gc "gopkg.in/check.v1" "github.com/juju/juju/controller" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig/podcfg" "github.com/juju/juju/testing" ) diff --git a/internal/cloudconfig/userdatacfg_test.go b/internal/cloudconfig/userdatacfg_test.go index 74970e65cbf..ae1c0ab5a02 100644 --- a/internal/cloudconfig/userdatacfg_test.go +++ b/internal/cloudconfig/userdatacfg_test.go @@ -16,7 +16,6 @@ import ( "time" "github.com/juju/collections/set" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" "github.com/juju/proxy" @@ -35,6 +34,7 @@ import ( "github.com/juju/juju/core/paths" "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/imagemetadata" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig" "github.com/juju/juju/internal/cloudconfig/cloudinit" "github.com/juju/juju/internal/cloudconfig/instancecfg" diff --git a/internal/cloudconfig/userdatacfg_unix.go b/internal/cloudconfig/userdatacfg_unix.go index 0ce855be729..a9a25b6acd2 100644 --- a/internal/cloudconfig/userdatacfg_unix.go +++ b/internal/cloudconfig/userdatacfg_unix.go @@ -16,7 +16,6 @@ import ( "github.com/juju/errors" "github.com/juju/featureflag" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" "github.com/juju/proxy" @@ -26,6 +25,7 @@ import ( "github.com/juju/juju/core/os/ostype" "github.com/juju/juju/environs/bootstrap" "github.com/juju/juju/environs/simplestreams" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig/cloudinit" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/juju/osenv" diff --git a/internal/container/broker/lxd-broker_test.go b/internal/container/broker/lxd-broker_test.go index 58a1353f4ea..42254a389f2 100644 --- a/internal/container/broker/lxd-broker_test.go +++ b/internal/container/broker/lxd-broker_test.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -26,6 +25,7 @@ import ( corenetwork "github.com/juju/juju/core/network" "github.com/juju/juju/environs" "github.com/juju/juju/environs/envcontext" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig" "github.com/juju/juju/internal/cloudconfig/instancecfg" "github.com/juju/juju/internal/container" diff --git a/internal/migration/migration.go b/internal/migration/migration.go index 15a510777b6..703e836d5dc 100644 --- a/internal/migration/migration.go +++ b/internal/migration/migration.go @@ -14,7 +14,6 @@ import ( "github.com/juju/description/v6" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/naturalsort" "github.com/juju/version/v2" @@ -28,6 +27,7 @@ import ( migrations "github.com/juju/juju/domain/modelmigration" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/servicefactory" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/tools" diff --git a/internal/migration/migration_test.go b/internal/migration/migration_test.go index 78d15129eeb..87271f1764b 100644 --- a/internal/migration/migration_test.go +++ b/internal/migration/migration_test.go @@ -12,7 +12,6 @@ import ( "github.com/juju/description/v6" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -26,6 +25,7 @@ import ( resourcetesting "github.com/juju/juju/core/resources/testing" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/migration" "github.com/juju/juju/internal/storage" diff --git a/internal/resource/charmhub.go b/internal/resource/charmhub.go index 08f9f073121..81e898efe65 100644 --- a/internal/resource/charmhub.go +++ b/internal/resource/charmhub.go @@ -8,10 +8,10 @@ import ( "net/url" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/kr/pretty" corelogger "github.com/juju/juju/core/logger" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/charmhub/transport" "github.com/juju/juju/state" diff --git a/internal/resource/charmhub_test.go b/internal/resource/charmhub_test.go index d809e96f28f..bd05d12ac90 100644 --- a/internal/resource/charmhub_test.go +++ b/internal/resource/charmhub_test.go @@ -7,12 +7,12 @@ import ( "bytes" "io" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub/transport" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/resource" diff --git a/internal/resource/export_test.go b/internal/resource/export_test.go index 055837a99d6..b2c1ee1860d 100644 --- a/internal/resource/export_test.go +++ b/internal/resource/export_test.go @@ -6,10 +6,10 @@ package resource import ( "time" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" corelogger "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/internal/resource/interfaces.go b/internal/resource/interfaces.go index 5a8200a9567..1fc991d2a0b 100644 --- a/internal/resource/interfaces.go +++ b/internal/resource/interfaces.go @@ -8,9 +8,8 @@ import ( "io" "net/url" - charmresource "github.com/juju/juju/internal/charm/resource" - "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/charmhub" "github.com/juju/juju/internal/charmhub/transport" "github.com/juju/juju/state" diff --git a/internal/resource/opener.go b/internal/resource/opener.go index 15966f427c5..168be1245d3 100644 --- a/internal/resource/opener.go +++ b/internal/resource/opener.go @@ -9,12 +9,12 @@ import ( "github.com/im7mortal/kmutex" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/resources" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/state" ) diff --git a/internal/resource/opener_test.go b/internal/resource/opener_test.go index 8e47e00eb1c..3fcc950dea0 100644 --- a/internal/resource/opener_test.go +++ b/internal/resource/opener_test.go @@ -10,14 +10,14 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" "github.com/juju/juju/core/resources" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/resource" "github.com/juju/juju/internal/resource/mocks" "github.com/juju/juju/state" diff --git a/internal/resource/resource.go b/internal/resource/resource.go index 1267446170a..029f45528a3 100644 --- a/internal/resource/resource.go +++ b/internal/resource/resource.go @@ -7,7 +7,6 @@ import ( "io" charmresource "github.com/juju/juju/internal/charm/resource" - internallogger "github.com/juju/juju/internal/logger" ) diff --git a/internal/resource/retryclient.go b/internal/resource/retryclient.go index 8ccc339df42..eeb25ec454a 100644 --- a/internal/resource/retryclient.go +++ b/internal/resource/retryclient.go @@ -9,8 +9,9 @@ import ( "github.com/juju/clock" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/retry" + + "github.com/juju/juju/internal/charm" ) // ResourceRetryClient is a wrapper around a Juju repository client that diff --git a/internal/worker/bootstrap/deployer.go b/internal/worker/bootstrap/deployer.go index 8d995503529..2a1e707e3da 100644 --- a/internal/worker/bootstrap/deployer.go +++ b/internal/worker/bootstrap/deployer.go @@ -9,7 +9,6 @@ import ( "os" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" "github.com/juju/juju/controller" @@ -20,6 +19,7 @@ import ( "github.com/juju/juju/core/objectstore" "github.com/juju/juju/environs" "github.com/juju/juju/internal/bootstrap" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/charm/services" "github.com/juju/juju/state" "github.com/juju/juju/state/binarystorage" diff --git a/internal/worker/bootstrap/worker_test.go b/internal/worker/bootstrap/worker_test.go index dfda04017f8..e5f07d91399 100644 --- a/internal/worker/bootstrap/worker_test.go +++ b/internal/worker/bootstrap/worker_test.go @@ -9,7 +9,6 @@ import ( "path/filepath" "time" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4" "github.com/juju/worker/v4/workertest" @@ -29,6 +28,7 @@ import ( "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" "github.com/juju/juju/internal/bootstrap" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig" "github.com/juju/juju/internal/cloudconfig/instancecfg" "github.com/juju/juju/internal/storage" diff --git a/internal/worker/caasapplicationprovisioner/ops.go b/internal/worker/caasapplicationprovisioner/ops.go index 35e1b261ea9..2825087e1f4 100644 --- a/internal/worker/caasapplicationprovisioner/ops.go +++ b/internal/worker/caasapplicationprovisioner/ops.go @@ -12,7 +12,6 @@ import ( "github.com/juju/clock" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/retry" @@ -20,6 +19,7 @@ import ( "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/cloudconfig/podcfg" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/caasapplicationprovisioner/ops_test.go b/internal/worker/caasapplicationprovisioner/ops_test.go index 5b49388a427..b2ec22aaabb 100644 --- a/internal/worker/caasapplicationprovisioner/ops_test.go +++ b/internal/worker/caasapplicationprovisioner/ops_test.go @@ -6,7 +6,6 @@ package caasapplicationprovisioner_test import ( "github.com/juju/clock/testclock" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -25,6 +24,7 @@ import ( "github.com/juju/juju/core/network" "github.com/juju/juju/core/resources" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/worker/caasapplicationprovisioner" diff --git a/internal/worker/caasfirewaller/worker.go b/internal/worker/caasfirewaller/worker.go index 3ccd12c389d..2814f4ba970 100644 --- a/internal/worker/caasfirewaller/worker.go +++ b/internal/worker/caasfirewaller/worker.go @@ -5,12 +5,12 @@ package caasfirewaller import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/worker/v4" "github.com/juju/worker/v4/catacomb" "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" ) // Config holds configuration for the CAAS unit firewaller worker. diff --git a/internal/worker/caasfirewaller/worker_test.go b/internal/worker/caasfirewaller/worker_test.go index 72123faaaa9..381646ad257 100644 --- a/internal/worker/caasfirewaller/worker_test.go +++ b/internal/worker/caasfirewaller/worker_test.go @@ -5,7 +5,6 @@ package caasfirewaller_test import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4" "github.com/juju/worker/v4/workertest" @@ -17,6 +16,7 @@ import ( "github.com/juju/juju/core/logger" "github.com/juju/juju/core/watcher" "github.com/juju/juju/core/watcher/watchertest" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/caasfirewaller" "github.com/juju/juju/internal/worker/caasfirewaller/mocks" diff --git a/internal/worker/firewaller/firewaller.go b/internal/worker/firewaller/firewaller.go index 7995555ccb6..648c1e59974 100644 --- a/internal/worker/firewaller/firewaller.go +++ b/internal/worker/firewaller/firewaller.go @@ -14,7 +14,6 @@ import ( "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/worker/v4" "github.com/juju/worker/v4/catacomb" @@ -31,6 +30,7 @@ import ( "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/instances" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/worker/common" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/uniter/api/interface.go b/internal/worker/uniter/api/interface.go index 5b16524eaa5..90b689b8935 100644 --- a/internal/worker/uniter/api/interface.go +++ b/internal/worker/uniter/api/interface.go @@ -6,7 +6,6 @@ package api import ( stdcontext "context" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/agent/uniter" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/uniter/charm/bundles.go b/internal/worker/uniter/charm/bundles.go index 1866a35c540..4470a27ebb0 100644 --- a/internal/worker/uniter/charm/bundles.go +++ b/internal/worker/uniter/charm/bundles.go @@ -9,9 +9,9 @@ import ( "path" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/downloader" ) diff --git a/internal/worker/uniter/charm/bundles_test.go b/internal/worker/uniter/charm/bundles_test.go index b8c1c390823..47fe4a3adc6 100644 --- a/internal/worker/uniter/charm/bundles_test.go +++ b/internal/worker/uniter/charm/bundles_test.go @@ -11,12 +11,12 @@ import ( "regexp" "github.com/juju/errors" - jujucharm "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" gc "gopkg.in/check.v1" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/downloader" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/charm" diff --git a/internal/worker/uniter/charm/charm_test.go b/internal/worker/uniter/charm/charm_test.go index dc00ef8c594..726c4b3749b 100644 --- a/internal/worker/uniter/charm/charm_test.go +++ b/internal/worker/uniter/charm/charm_test.go @@ -9,10 +9,10 @@ import ( "path/filepath" "github.com/juju/collections/set" - jujucharm "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/worker/uniter/charm" "github.com/juju/juju/testcharms" ) diff --git a/internal/worker/uniter/charm/manifest_deployer.go b/internal/worker/uniter/charm/manifest_deployer.go index 22e86ad8b8f..25ff90e4e92 100644 --- a/internal/worker/uniter/charm/manifest_deployer.go +++ b/internal/worker/uniter/charm/manifest_deployer.go @@ -12,11 +12,11 @@ import ( "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/retry" "github.com/juju/utils/v4" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" ) const ( diff --git a/internal/worker/uniter/container/workload.go b/internal/worker/uniter/container/workload.go index 2e51a0e045e..64ac7339473 100644 --- a/internal/worker/uniter/container/workload.go +++ b/internal/worker/uniter/container/workload.go @@ -9,9 +9,9 @@ import ( "sync" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/entity_mocks_test.go b/internal/worker/uniter/entity_mocks_test.go index 89b5a5dddbd..03ebae1591e 100644 --- a/internal/worker/uniter/entity_mocks_test.go +++ b/internal/worker/uniter/entity_mocks_test.go @@ -8,7 +8,6 @@ import ( "fmt" "sync" - jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -20,6 +19,7 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/core/network" "github.com/juju/juju/core/status" + jujucharm "github.com/juju/juju/internal/charm" uniterapi "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/uniter/hook/hook.go b/internal/worker/uniter/hook/hook.go index c04ca1bf94c..fbc03eb9d70 100644 --- a/internal/worker/uniter/hook/hook.go +++ b/internal/worker/uniter/hook/hook.go @@ -7,10 +7,10 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm/hooks" ) // Info holds details required to execute a hook. Not all fields are diff --git a/internal/worker/uniter/hook/hook_test.go b/internal/worker/uniter/hook/hook_test.go index ea61f59cf81..bcfda6bfe41 100644 --- a/internal/worker/uniter/hook/hook_test.go +++ b/internal/worker/uniter/hook/hook_test.go @@ -4,10 +4,10 @@ package hook_test import ( - "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/testing" ) diff --git a/internal/worker/uniter/leadership/resolver.go b/internal/worker/uniter/leadership/resolver.go index f124ee33491..7113540faf2 100644 --- a/internal/worker/uniter/leadership/resolver.go +++ b/internal/worker/uniter/leadership/resolver.go @@ -6,10 +6,9 @@ package leadership import ( "context" - "github.com/juju/juju/internal/charm/hooks" - "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/leadership/resolver_test.go b/internal/worker/uniter/leadership/resolver_test.go index 726e49a2a3e..05e34cd5f74 100644 --- a/internal/worker/uniter/leadership/resolver_test.go +++ b/internal/worker/uniter/leadership/resolver_test.go @@ -6,12 +6,12 @@ package leadership_test import ( "context" - "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" "github.com/juju/juju/core/life" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/leadership" diff --git a/internal/worker/uniter/mockrunner_test.go b/internal/worker/uniter/mockrunner_test.go index 257107ddf4f..d1cda7f7daf 100644 --- a/internal/worker/uniter/mockrunner_test.go +++ b/internal/worker/uniter/mockrunner_test.go @@ -10,10 +10,10 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" utilexec "github.com/juju/utils/v4/exec" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/runner" "github.com/juju/juju/internal/worker/uniter/runner/context" ) diff --git a/internal/worker/uniter/op_callbacks.go b/internal/worker/uniter/op_callbacks.go index 28924036c43..19198310cfe 100644 --- a/internal/worker/uniter/op_callbacks.go +++ b/internal/worker/uniter/op_callbacks.go @@ -8,11 +8,11 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/core/model" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/charm" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/operation/deploy.go b/internal/worker/uniter/operation/deploy.go index 16231642b1b..bad5ea98611 100644 --- a/internal/worker/uniter/operation/deploy.go +++ b/internal/worker/uniter/operation/deploy.go @@ -8,8 +8,8 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/charm" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/operation/deploy_test.go b/internal/worker/uniter/operation/deploy_test.go index 1a97b0b19f5..43d40198482 100644 --- a/internal/worker/uniter/operation/deploy_test.go +++ b/internal/worker/uniter/operation/deploy_test.go @@ -7,11 +7,11 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/operation/executor_test.go b/internal/worker/uniter/operation/executor_test.go index 2b7a3f4da74..53b6a338ce1 100644 --- a/internal/worker/uniter/operation/executor_test.go +++ b/internal/worker/uniter/operation/executor_test.go @@ -8,13 +8,13 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" "gopkg.in/yaml.v2" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/operation/factory_test.go b/internal/worker/uniter/operation/factory_test.go index 34b7900a2dc..5e6872236a7 100644 --- a/internal/worker/uniter/operation/factory_test.go +++ b/internal/worker/uniter/operation/factory_test.go @@ -6,7 +6,6 @@ package operation_test import ( "context" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -15,6 +14,7 @@ import ( "github.com/juju/juju/api/agent/uniter" basetesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/common/charmrunner" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/operation/failaction_test.go b/internal/worker/uniter/operation/failaction_test.go index e439cfbcbb6..bc313843ac1 100644 --- a/internal/worker/uniter/operation/failaction_test.go +++ b/internal/worker/uniter/operation/failaction_test.go @@ -7,11 +7,11 @@ import ( stdcontext "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" ) diff --git a/internal/worker/uniter/operation/leader.go b/internal/worker/uniter/operation/leader.go index 056f4b7a204..cc5d98e2fba 100644 --- a/internal/worker/uniter/operation/leader.go +++ b/internal/worker/uniter/operation/leader.go @@ -7,9 +7,9 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/remotestate" ) diff --git a/internal/worker/uniter/operation/leader_test.go b/internal/worker/uniter/operation/leader_test.go index e625ab3c409..20fef4d2fd1 100644 --- a/internal/worker/uniter/operation/leader_test.go +++ b/internal/worker/uniter/operation/leader_test.go @@ -6,11 +6,11 @@ package operation_test import ( stdcontext "context" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/operation/remoteinit_test.go b/internal/worker/uniter/operation/remoteinit_test.go index 8bb8f92cd32..646bdb1213e 100644 --- a/internal/worker/uniter/operation/remoteinit_test.go +++ b/internal/worker/uniter/operation/remoteinit_test.go @@ -7,11 +7,11 @@ import ( stdcontext "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/operation/runaction_test.go b/internal/worker/uniter/operation/runaction_test.go index 7d1661d04e8..53b00355583 100644 --- a/internal/worker/uniter/operation/runaction_test.go +++ b/internal/worker/uniter/operation/runaction_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/api/agent/uniter" basetesting "github.com/juju/juju/api/base/testing" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/common/charmrunner" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/operation/runhook.go b/internal/worker/uniter/operation/runhook.go index 5657eb5fdec..95fd3d14121 100644 --- a/internal/worker/uniter/operation/runhook.go +++ b/internal/worker/uniter/operation/runhook.go @@ -8,13 +8,13 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" "github.com/juju/juju/core/relation" "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/common/charmrunner" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/operation/runhook_test.go b/internal/worker/uniter/operation/runhook_test.go index 96d58adf1a6..cf88593898c 100644 --- a/internal/worker/uniter/operation/runhook_test.go +++ b/internal/worker/uniter/operation/runhook_test.go @@ -7,12 +7,12 @@ import ( stdcontext "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/relation" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/common/charmrunner" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/operation/state_test.go b/internal/worker/uniter/operation/state_test.go index 5977e240f4b..db5ad1d1fdd 100644 --- a/internal/worker/uniter/operation/state_test.go +++ b/internal/worker/uniter/operation/state_test.go @@ -5,12 +5,12 @@ package operation_test import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" "gopkg.in/yaml.v2" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/operation/mocks" diff --git a/internal/worker/uniter/operation/util_test.go b/internal/worker/uniter/operation/util_test.go index 0c4100a3141..4cc43137732 100644 --- a/internal/worker/uniter/operation/util_test.go +++ b/internal/worker/uniter/operation/util_test.go @@ -8,7 +8,6 @@ import ( "sync" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" utilexec "github.com/juju/utils/v4/exec" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/core/relation" "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/charm" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/reboot/resolver.go b/internal/worker/uniter/reboot/resolver.go index 9211eb6a633..4cbbf13ec3e 100644 --- a/internal/worker/uniter/reboot/resolver.go +++ b/internal/worker/uniter/reboot/resolver.go @@ -7,10 +7,10 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/relation/mock_test.go b/internal/worker/uniter/relation/mock_test.go index c90ed7a6e38..1944924dd60 100644 --- a/internal/worker/uniter/relation/mock_test.go +++ b/internal/worker/uniter/relation/mock_test.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/juju/juju/internal/charm/hooks" - "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/relation/relationer.go b/internal/worker/uniter/relation/relationer.go index 78ed7e7f513..a267deff824 100644 --- a/internal/worker/uniter/relation/relationer.go +++ b/internal/worker/uniter/relation/relationer.go @@ -8,10 +8,10 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/worker/v4/dependency" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/runner/context" diff --git a/internal/worker/uniter/relation/relationer_test.go b/internal/worker/uniter/relation/relationer_test.go index d5fe967b654..384221d2101 100644 --- a/internal/worker/uniter/relation/relationer_test.go +++ b/internal/worker/uniter/relation/relationer_test.go @@ -8,13 +8,13 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" apiuniter "github.com/juju/juju/api/agent/uniter" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/relation/resolver.go b/internal/worker/uniter/relation/resolver.go index c9434cf7dbc..04be568bc17 100644 --- a/internal/worker/uniter/relation/resolver.go +++ b/internal/worker/uniter/relation/resolver.go @@ -8,12 +8,12 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/kr/pretty" "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/relation/resolver_test.go b/internal/worker/uniter/relation/resolver_test.go index eea907eaca3..2c2e4c9346f 100644 --- a/internal/worker/uniter/relation/resolver_test.go +++ b/internal/worker/uniter/relation/resolver_test.go @@ -10,8 +10,6 @@ import ( "sync/atomic" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -23,6 +21,8 @@ import ( apitesting "github.com/juju/juju/api/base/testing" apiservererrors "github.com/juju/juju/apiserver/errors" "github.com/juju/juju/core/life" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" uniterapi "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/relation/state.go b/internal/worker/uniter/relation/state.go index 71f89475f92..421a77df5b3 100644 --- a/internal/worker/uniter/relation/state.go +++ b/internal/worker/uniter/relation/state.go @@ -7,11 +7,11 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/kr/pretty" "gopkg.in/yaml.v2" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" ) diff --git a/internal/worker/uniter/relation/state_test.go b/internal/worker/uniter/relation/state_test.go index a59fed560cc..905d5e0c9f1 100644 --- a/internal/worker/uniter/relation/state_test.go +++ b/internal/worker/uniter/relation/state_test.go @@ -6,10 +6,10 @@ package relation_test import ( "fmt" - "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/relation" diff --git a/internal/worker/uniter/relation/statetracker.go b/internal/worker/uniter/relation/statetracker.go index ee395aad590..fecb750b53c 100644 --- a/internal/worker/uniter/relation/statetracker.go +++ b/internal/worker/uniter/relation/statetracker.go @@ -10,8 +10,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/worker/v4" "github.com/kr/pretty" @@ -19,6 +17,8 @@ import ( "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/relation" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/relation/statetracker_test.go b/internal/worker/uniter/relation/statetracker_test.go index 434bc307965..4094ed3526f 100644 --- a/internal/worker/uniter/relation/statetracker_test.go +++ b/internal/worker/uniter/relation/statetracker_test.go @@ -10,8 +10,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -23,6 +21,8 @@ import ( corerelation "github.com/juju/juju/core/relation" "github.com/juju/juju/core/watcher" "github.com/juju/juju/core/watcher/watchertest" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/relation" diff --git a/internal/worker/uniter/resolver.go b/internal/worker/uniter/resolver.go index 90a60a542a0..c0dfeef2a91 100644 --- a/internal/worker/uniter/resolver.go +++ b/internal/worker/uniter/resolver.go @@ -8,12 +8,12 @@ import ( "fmt" "github.com/juju/errors" - jujucharm "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" + jujucharm "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/resolver/locker_test.go b/internal/worker/uniter/resolver/locker_test.go index 536b6190ebd..35df2a603a9 100644 --- a/internal/worker/uniter/resolver/locker_test.go +++ b/internal/worker/uniter/resolver/locker_test.go @@ -4,11 +4,11 @@ package resolver_test import ( - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/fortress" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/resolver/loop.go b/internal/worker/uniter/resolver/loop.go index 08e6279d1ba..6cc7def161e 100644 --- a/internal/worker/uniter/resolver/loop.go +++ b/internal/worker/uniter/resolver/loop.go @@ -8,12 +8,12 @@ import ( "time" "github.com/juju/errors" - jujucharm "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/mutex/v2" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/lxdprofile" + jujucharm "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/fortress" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/resolver/loop_test.go b/internal/worker/uniter/resolver/loop_test.go index ced20e0a63e..b38e120cda7 100644 --- a/internal/worker/uniter/resolver/loop_test.go +++ b/internal/worker/uniter/resolver/loop_test.go @@ -8,12 +8,12 @@ import ( "errors" "time" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/mutex/v2" envtesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/resolver/opfactory.go b/internal/worker/uniter/resolver/opfactory.go index 8435e0f754d..cbef1ffbf2d 100644 --- a/internal/worker/uniter/resolver/opfactory.go +++ b/internal/worker/uniter/resolver/opfactory.go @@ -7,9 +7,9 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/resolver/opfactory_test.go b/internal/worker/uniter/resolver/opfactory_test.go index 4b74400dd40..3ddd849831f 100644 --- a/internal/worker/uniter/resolver/opfactory_test.go +++ b/internal/worker/uniter/resolver/opfactory_test.go @@ -7,11 +7,11 @@ import ( "context" "errors" - "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/resolver_test.go b/internal/worker/uniter/resolver_test.go index 5b37707bd77..894acd9aeaa 100644 --- a/internal/worker/uniter/resolver_test.go +++ b/internal/worker/uniter/resolver_test.go @@ -8,13 +8,13 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter" uniteractions "github.com/juju/juju/internal/worker/uniter/actions" diff --git a/internal/worker/uniter/runner/context/context.go b/internal/worker/uniter/runner/context/context.go index f5b495f60f8..75cd4f57aea 100644 --- a/internal/worker/uniter/runner/context/context.go +++ b/internal/worker/uniter/runner/context/context.go @@ -13,8 +13,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/proxy" @@ -29,6 +27,8 @@ import ( "github.com/juju/juju/core/status" coretrace "github.com/juju/juju/core/trace" secreterrors "github.com/juju/juju/domain/secret/errors" + "github.com/juju/juju/internal/charm" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/worker/common/charmrunner" "github.com/juju/juju/internal/worker/uniter/api" diff --git a/internal/worker/uniter/runner/context/context_test.go b/internal/worker/uniter/runner/context/context_test.go index 29729478ba2..c2717028688 100644 --- a/internal/worker/uniter/runner/context/context_test.go +++ b/internal/worker/uniter/runner/context/context_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -26,6 +25,7 @@ import ( "github.com/juju/juju/core/quota" coresecrets "github.com/juju/juju/core/secrets" status2 "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/secrets" "github.com/juju/juju/internal/secrets/provider" "github.com/juju/juju/internal/secrets/provider/vault" diff --git a/internal/worker/uniter/runner/context/contextfactory.go b/internal/worker/uniter/runner/context/contextfactory.go index d74f93cc367..814b6b1df1f 100644 --- a/internal/worker/uniter/runner/context/contextfactory.go +++ b/internal/worker/uniter/runner/context/contextfactory.go @@ -10,7 +10,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/api/types" @@ -19,6 +18,7 @@ import ( "github.com/juju/juju/core/model" "github.com/juju/juju/core/network" "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/runner/context/payloads" diff --git a/internal/worker/uniter/runner/context/contextfactory_test.go b/internal/worker/uniter/runner/context/contextfactory_test.go index 10c5a654a48..57c77644256 100644 --- a/internal/worker/uniter/runner/context/contextfactory_test.go +++ b/internal/worker/uniter/runner/context/contextfactory_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/clock/testclock" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -17,6 +16,7 @@ import ( apiuniter "github.com/juju/juju/api/agent/uniter" "github.com/juju/juju/api/types" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/runner/context/payloads/context_test.go b/internal/worker/uniter/runner/context/payloads/context_test.go index 98bfb62af71..04ff5bd8924 100644 --- a/internal/worker/uniter/runner/context/payloads/context_test.go +++ b/internal/worker/uniter/runner/context/payloads/context_test.go @@ -4,13 +4,13 @@ package payloads_test import ( - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" corepayloads "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/worker/uniter/runner/context/mocks" "github.com/juju/juju/internal/worker/uniter/runner/context/payloads" ) diff --git a/internal/worker/uniter/runner/context/relation_test.go b/internal/worker/uniter/runner/context/relation_test.go index 14ddc9762ba..9b86e7e2271 100644 --- a/internal/worker/uniter/runner/context/relation_test.go +++ b/internal/worker/uniter/runner/context/relation_test.go @@ -6,7 +6,6 @@ package context_test import ( stdcontext "context" - "github.com/juju/juju/internal/charm" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -14,6 +13,7 @@ import ( apiuniter "github.com/juju/juju/api/agent/uniter" "github.com/juju/juju/core/relation" + "github.com/juju/juju/internal/charm" uniterapi "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/runner/context" "github.com/juju/juju/rpc/params" diff --git a/internal/worker/uniter/runner/context/resources/base_test.go b/internal/worker/uniter/runner/context/resources/base_test.go index 785ec0a59bf..aee3d5a4a6e 100644 --- a/internal/worker/uniter/runner/context/resources/base_test.go +++ b/internal/worker/uniter/runner/context/resources/base_test.go @@ -7,12 +7,12 @@ import ( "io" "time" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" gc "gopkg.in/check.v1" "github.com/juju/juju/core/resources" resourcetesting "github.com/juju/juju/core/resources/testing" + charmresource "github.com/juju/juju/internal/charm/resource" ) func newCharmResource(c *gc.C, stub *testing.Stub, name, content string, resType charmresource.Type) (resources.Resource, io.ReadCloser) { diff --git a/internal/worker/uniter/runner/context/resources/content.go b/internal/worker/uniter/runner/context/resources/content.go index 89e2a80498a..90c0e10afb4 100644 --- a/internal/worker/uniter/runner/context/resources/content.go +++ b/internal/worker/uniter/runner/context/resources/content.go @@ -8,8 +8,9 @@ import ( "io" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/utils/v4" + + charmresource "github.com/juju/juju/internal/charm/resource" ) // Content holds a reader for the content of a resource along diff --git a/internal/worker/uniter/runner/context/resources/content_test.go b/internal/worker/uniter/runner/context/resources/content_test.go index e3c11016b21..248819ffbd2 100644 --- a/internal/worker/uniter/runner/context/resources/content_test.go +++ b/internal/worker/uniter/runner/context/resources/content_test.go @@ -7,11 +7,11 @@ import ( "io" "strings" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/testing" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/worker/uniter/runner/context/resources" ) diff --git a/internal/worker/uniter/runner/context/resources/context.go b/internal/worker/uniter/runner/context/resources/context.go index d1c290d133b..44ebc12d5ca 100644 --- a/internal/worker/uniter/runner/context/resources/context.go +++ b/internal/worker/uniter/runner/context/resources/context.go @@ -10,9 +10,9 @@ import ( "path/filepath" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/core/logger" + charmresource "github.com/juju/juju/internal/charm/resource" ) // ResourcesHookContext is the implementation of runner.ContextResources. diff --git a/internal/worker/uniter/runner/context/resources/resource.go b/internal/worker/uniter/runner/context/resources/resource.go index a0cab1c3fd5..bb049e68e87 100644 --- a/internal/worker/uniter/runner/context/resources/resource.go +++ b/internal/worker/uniter/runner/context/resources/resource.go @@ -9,11 +9,11 @@ import ( "io" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "gopkg.in/httprequest.v1" "gopkg.in/yaml.v2" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/docker" ) diff --git a/internal/worker/uniter/runner/context/util_test.go b/internal/worker/uniter/runner/context/util_test.go index f82e931fdc0..536b34233d1 100644 --- a/internal/worker/uniter/runner/context/util_test.go +++ b/internal/worker/uniter/runner/context/util_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/juju/clock/testclock" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/proxy" jujutesting "github.com/juju/testing" @@ -20,6 +19,7 @@ import ( apiuniter "github.com/juju/juju/api/agent/uniter" "github.com/juju/juju/core/network" "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/uuid" uniterapi "github.com/juju/juju/internal/worker/uniter/api" diff --git a/internal/worker/uniter/runner/factory.go b/internal/worker/uniter/runner/factory.go index 45c1bec903c..7f9b09cdfbe 100644 --- a/internal/worker/uniter/runner/factory.go +++ b/internal/worker/uniter/runner/factory.go @@ -7,11 +7,11 @@ import ( stdcontext "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/api/agent/uniter" "github.com/juju/juju/core/actions" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/worker/common/charmrunner" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/runner/context" diff --git a/internal/worker/uniter/runner/factory_test.go b/internal/worker/uniter/runner/factory_test.go index 1caebe13c4f..cef7a0b70f5 100644 --- a/internal/worker/uniter/runner/factory_test.go +++ b/internal/worker/uniter/runner/factory_test.go @@ -7,13 +7,13 @@ import ( stdcontext "context" "strings" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" apiuniter "github.com/juju/juju/api/agent/uniter" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/common/charmrunner" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/runner" diff --git a/internal/worker/uniter/runner/jujuc/action-set.go b/internal/worker/uniter/runner/jujuc/action-set.go index 759062067c4..d6c10575ad7 100644 --- a/internal/worker/uniter/runner/jujuc/action-set.go +++ b/internal/worker/uniter/runner/jujuc/action-set.go @@ -9,9 +9,9 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/gnuflag" - "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" + "github.com/juju/juju/internal/charm" ) var keyRule = charm.GetActionNameRule() diff --git a/internal/worker/uniter/runner/jujuc/context.go b/internal/worker/uniter/runner/jujuc/context.go index 9165a8bb2ee..28da7f3dce1 100644 --- a/internal/worker/uniter/runner/jujuc/context.go +++ b/internal/worker/uniter/runner/jujuc/context.go @@ -11,7 +11,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/application" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/core/payloads" "github.com/juju/juju/core/relation" "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/uniter/runner/jujuc/jujuctesting/suite.go b/internal/worker/uniter/runner/jujuc/jujuctesting/suite.go index 358b3c5a529..c449ea3010f 100644 --- a/internal/worker/uniter/runner/jujuc/jujuctesting/suite.go +++ b/internal/worker/uniter/runner/jujuc/jujuctesting/suite.go @@ -6,11 +6,11 @@ package jujuctesting import ( "time" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" gc "gopkg.in/check.v1" "github.com/juju/juju/core/application" + "github.com/juju/juju/internal/charm" ) // ContextSuite is the base suite for testing jujuc.Context-related code. diff --git a/internal/worker/uniter/runner/jujuc/jujuctesting/unit.go b/internal/worker/uniter/runner/jujuc/jujuctesting/unit.go index 0b90691567b..b4f4f96dfc7 100644 --- a/internal/worker/uniter/runner/jujuc/jujuctesting/unit.go +++ b/internal/worker/uniter/runner/jujuc/jujuctesting/unit.go @@ -7,9 +7,9 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/application" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/uniter/runner/jujuc/payload-register.go b/internal/worker/uniter/runner/jujuc/payload-register.go index ac76e495e2a..19311d082c4 100644 --- a/internal/worker/uniter/runner/jujuc/payload-register.go +++ b/internal/worker/uniter/runner/jujuc/payload-register.go @@ -9,10 +9,10 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jujucmd "github.com/juju/juju/cmd" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" ) // NewPayloadRegisterCmd returns a new PayloadRegisterCmd that wraps the given context. diff --git a/internal/worker/uniter/runner/jujuc/payload-register_test.go b/internal/worker/uniter/runner/jujuc/payload-register_test.go index 8db22e1bbd4..b0c703f3f06 100644 --- a/internal/worker/uniter/runner/jujuc/payload-register_test.go +++ b/internal/worker/uniter/runner/jujuc/payload-register_test.go @@ -10,13 +10,13 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/testing" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/worker/uniter/runner/jujuc" "github.com/juju/juju/internal/worker/uniter/runner/jujuc/mocks" ) diff --git a/internal/worker/uniter/runner/jujuc/restricted.go b/internal/worker/uniter/runner/jujuc/restricted.go index d4892a51b42..69f5a62c8fd 100644 --- a/internal/worker/uniter/runner/jujuc/restricted.go +++ b/internal/worker/uniter/runner/jujuc/restricted.go @@ -8,13 +8,13 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/juju/core/application" "github.com/juju/juju/core/network" "github.com/juju/juju/core/payloads" "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/uniter/runner/runner_test.go b/internal/worker/uniter/runner/runner_test.go index a379a5db297..a7c1b44a341 100644 --- a/internal/worker/uniter/runner/runner_test.go +++ b/internal/worker/uniter/runner/runner_test.go @@ -13,7 +13,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/loggo/v2" envtesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -23,6 +22,7 @@ import ( "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/internal/worker/common/charmrunner" "github.com/juju/juju/internal/worker/uniter/hook" diff --git a/internal/worker/uniter/runner/util_test.go b/internal/worker/uniter/runner/util_test.go index 3c85e1bc8ef..5f90a52cb1e 100644 --- a/internal/worker/uniter/runner/util_test.go +++ b/internal/worker/uniter/runner/util_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/juju/clock/testclock" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jujutesting "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -23,6 +22,7 @@ import ( apiuniter "github.com/juju/juju/api/agent/uniter" "github.com/juju/juju/api/types" "github.com/juju/juju/core/network" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" uniterapi "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/runner" diff --git a/internal/worker/uniter/secrets/resolver.go b/internal/worker/uniter/secrets/resolver.go index 848b1d039c8..5c3af907b7a 100644 --- a/internal/worker/uniter/secrets/resolver.go +++ b/internal/worker/uniter/secrets/resolver.go @@ -10,10 +10,10 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/secrets/resolver_test.go b/internal/worker/uniter/secrets/resolver_test.go index c9b4d035e60..44e7e3d6eb9 100644 --- a/internal/worker/uniter/secrets/resolver_test.go +++ b/internal/worker/uniter/secrets/resolver_test.go @@ -6,13 +6,13 @@ package secrets_test import ( "context" - "github.com/juju/juju/internal/charm/hooks" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" "github.com/juju/juju/core/life" coresecrets "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/secrets/secrets_test.go b/internal/worker/uniter/secrets/secrets_test.go index b759ee4d4f4..d8a1263f83c 100644 --- a/internal/worker/uniter/secrets/secrets_test.go +++ b/internal/worker/uniter/secrets/secrets_test.go @@ -6,7 +6,6 @@ package secrets_test import ( "context" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -14,6 +13,7 @@ import ( "gopkg.in/yaml.v2" coresecrets "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" operationmocks "github.com/juju/juju/internal/worker/uniter/operation/mocks" diff --git a/internal/worker/uniter/secrets/state.go b/internal/worker/uniter/secrets/state.go index 3b67bde8b35..438fbf27072 100644 --- a/internal/worker/uniter/secrets/state.go +++ b/internal/worker/uniter/secrets/state.go @@ -6,9 +6,9 @@ package secrets import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "gopkg.in/yaml.v2" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/uniter/storage/attachments.go b/internal/worker/uniter/storage/attachments.go index 620bec8f95b..65bebaa76b1 100644 --- a/internal/worker/uniter/storage/attachments.go +++ b/internal/worker/uniter/storage/attachments.go @@ -5,9 +5,9 @@ package storage import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/api" "github.com/juju/juju/internal/worker/uniter/hook" ) diff --git a/internal/worker/uniter/storage/attachments_test.go b/internal/worker/uniter/storage/attachments_test.go index 77809ebb197..cd2f2462a7b 100644 --- a/internal/worker/uniter/storage/attachments_test.go +++ b/internal/worker/uniter/storage/attachments_test.go @@ -6,7 +6,6 @@ package storage_test import ( "context" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -14,6 +13,7 @@ import ( "github.com/juju/juju/core/life" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/storage/resolver.go b/internal/worker/uniter/storage/resolver.go index fa0e4ee2ec7..e0956974093 100644 --- a/internal/worker/uniter/storage/resolver.go +++ b/internal/worker/uniter/storage/resolver.go @@ -7,12 +7,12 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" "github.com/juju/juju/core/life" "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/storage/state.go b/internal/worker/uniter/storage/state.go index 26c1e7d5431..c57e55d4de6 100644 --- a/internal/worker/uniter/storage/state.go +++ b/internal/worker/uniter/storage/state.go @@ -5,9 +5,9 @@ package storage import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "gopkg.in/yaml.v2" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/rpc/params" ) diff --git a/internal/worker/uniter/storage/state_test.go b/internal/worker/uniter/storage/state_test.go index 07648a703b3..5609b7dfd4b 100644 --- a/internal/worker/uniter/storage/state_test.go +++ b/internal/worker/uniter/storage/state_test.go @@ -5,11 +5,11 @@ package storage_test import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/storage" ) diff --git a/internal/worker/uniter/uniter.go b/internal/worker/uniter/uniter.go index fdd3d9ad0a0..0797d3ca588 100644 --- a/internal/worker/uniter/uniter.go +++ b/internal/worker/uniter/uniter.go @@ -11,7 +11,6 @@ import ( "github.com/juju/clock" "github.com/juju/errors" - jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" "github.com/juju/utils/v4" "github.com/juju/utils/v4/exec" @@ -30,6 +29,7 @@ import ( "github.com/juju/juju/core/status" coretrace "github.com/juju/juju/core/trace" "github.com/juju/juju/core/watcher" + jujucharm "github.com/juju/juju/internal/charm" jworker "github.com/juju/juju/internal/worker" "github.com/juju/juju/internal/worker/fortress" "github.com/juju/juju/internal/worker/uniter/actions" diff --git a/internal/worker/uniter/uniter_test.go b/internal/worker/uniter/uniter_test.go index 6516ed47fd8..2833825bf01 100644 --- a/internal/worker/uniter/uniter_test.go +++ b/internal/worker/uniter/uniter_test.go @@ -12,7 +12,6 @@ import ( "syscall" "github.com/juju/errors" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/loggo/v2" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -23,6 +22,7 @@ import ( "github.com/juju/juju/core/life" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker" "github.com/juju/juju/internal/worker/uniter" uniterapi "github.com/juju/juju/internal/worker/uniter/api" diff --git a/internal/worker/uniter/upgradeseries/resolver.go b/internal/worker/uniter/upgradeseries/resolver.go index e9c3685326c..6b7f3f6ba49 100644 --- a/internal/worker/uniter/upgradeseries/resolver.go +++ b/internal/worker/uniter/upgradeseries/resolver.go @@ -6,10 +6,9 @@ package upgradeseries import ( "context" - "github.com/juju/juju/internal/charm/hooks" - "github.com/juju/juju/core/logger" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" diff --git a/internal/worker/uniter/upgradeseries/resolver_test.go b/internal/worker/uniter/upgradeseries/resolver_test.go index 23835856ddd..c37cc281b9a 100644 --- a/internal/worker/uniter/upgradeseries/resolver_test.go +++ b/internal/worker/uniter/upgradeseries/resolver_test.go @@ -6,12 +6,12 @@ package upgradeseries_test import ( "context" - "github.com/juju/juju/internal/charm/hooks" "github.com/juju/testing" "go.uber.org/mock/gomock" gc "gopkg.in/check.v1" "github.com/juju/juju/core/model" + "github.com/juju/juju/internal/charm/hooks" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/worker/uniter/hook" "github.com/juju/juju/internal/worker/uniter/operation" diff --git a/internal/worker/uniter/util_test.go b/internal/worker/uniter/util_test.go index 6da814eb086..b6cd74a8452 100644 --- a/internal/worker/uniter/util_test.go +++ b/internal/worker/uniter/util_test.go @@ -22,7 +22,6 @@ import ( "github.com/juju/clock/testclock" "github.com/juju/collections/set" "github.com/juju/errors" - jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/mutex/v2" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" @@ -44,6 +43,7 @@ import ( "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" "github.com/juju/juju/core/watcher/watchertest" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/downloader" loggertesting "github.com/juju/juju/internal/logger/testing" jworker "github.com/juju/juju/internal/worker" diff --git a/internal/worker/uniter/verifycharmprofile/verifycharmprofile.go b/internal/worker/uniter/verifycharmprofile/verifycharmprofile.go index e54897d0645..8473efe22e2 100644 --- a/internal/worker/uniter/verifycharmprofile/verifycharmprofile.go +++ b/internal/worker/uniter/verifycharmprofile/verifycharmprofile.go @@ -6,11 +6,10 @@ package verifycharmprofile import ( "context" - jujucharm "github.com/juju/juju/internal/charm" - "github.com/juju/juju/core/logger" "github.com/juju/juju/core/lxdprofile" "github.com/juju/juju/core/model" + jujucharm "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/worker/uniter/operation" "github.com/juju/juju/internal/worker/uniter/remotestate" "github.com/juju/juju/internal/worker/uniter/resolver" diff --git a/juju/testing/utils.go b/juju/testing/utils.go index c52a41e6ce9..66b3fdd65b3 100644 --- a/juju/testing/utils.go +++ b/juju/testing/utils.go @@ -9,7 +9,6 @@ import ( "os" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" gc "gopkg.in/check.v1" @@ -18,6 +17,7 @@ import ( "github.com/juju/juju/core/lxdprofile" "github.com/juju/juju/core/network" coreobjectstore "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/objectstore" objectstoretesting "github.com/juju/juju/internal/objectstore/testing" diff --git a/rpc/params/crossmodel.go b/rpc/params/crossmodel.go index 09dff892304..de69f4c19f9 100644 --- a/rpc/params/crossmodel.go +++ b/rpc/params/crossmodel.go @@ -5,11 +5,11 @@ package params import ( "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" - "github.com/juju/juju/internal/charm" "github.com/kr/pretty" "gopkg.in/macaroon.v2" "github.com/juju/juju/core/life" + "github.com/juju/juju/internal/charm" ) // ExternalControllerInfoResults contains the results of querying diff --git a/rpc/params/multiwatcher.go b/rpc/params/multiwatcher.go index 68bddfefdf1..f10661ffaa4 100644 --- a/rpc/params/multiwatcher.go +++ b/rpc/params/multiwatcher.go @@ -10,13 +10,13 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/juju/core/constraints" "github.com/juju/juju/core/instance" "github.com/juju/juju/core/life" "github.com/juju/juju/core/model" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" ) // EntityInfo is implemented by all entity Info types. diff --git a/rpc/params/params_test.go b/rpc/params/params_test.go index 845e5b629cf..764c339705d 100644 --- a/rpc/params/params_test.go +++ b/rpc/params/params_test.go @@ -7,7 +7,6 @@ import ( "encoding/json" stdtesting "testing" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/core/life" "github.com/juju/juju/core/model" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/rpc/params" "github.com/juju/juju/testing" ) diff --git a/state/allwatcher.go b/state/allwatcher.go index 26ee3e44e99..8fb96934dfd 100644 --- a/state/allwatcher.go +++ b/state/allwatcher.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/core/permission" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state/watcher" ) diff --git a/state/allwatcher_internal_test.go b/state/allwatcher_internal_test.go index 2108f616468..95cf1f0144a 100644 --- a/state/allwatcher_internal_test.go +++ b/state/allwatcher_internal_test.go @@ -11,7 +11,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" @@ -34,6 +33,7 @@ import ( "github.com/juju/juju/environs" environsconfig "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/envcontext" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/uuid" "github.com/juju/juju/state/watcher" diff --git a/state/application.go b/state/application.go index 7f12179cb90..3ea832c2f39 100644 --- a/state/application.go +++ b/state/application.go @@ -13,7 +13,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -34,6 +33,7 @@ import ( "github.com/juju/juju/core/network/firewall" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" mgoutils "github.com/juju/juju/internal/mongo/utils" internalpassword "github.com/juju/juju/internal/password" "github.com/juju/juju/internal/tools" diff --git a/state/application_test.go b/state/application_test.go index 95ad086e9d2..8d1d9ad3e8a 100644 --- a/state/application_test.go +++ b/state/application_test.go @@ -12,7 +12,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -38,6 +37,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" domainstorage "github.com/juju/juju/domain/storage" + "github.com/juju/juju/internal/charm" objectstoretesting "github.com/juju/juju/internal/objectstore/testing" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/storage/provider/dummy" diff --git a/state/applicationoffers.go b/state/applicationoffers.go index b3b1bcda91f..7333e2ac621 100644 --- a/state/applicationoffers.go +++ b/state/applicationoffers.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -20,6 +19,7 @@ import ( "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/permission" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/uuid" ) diff --git a/state/applicationoffers_test.go b/state/applicationoffers_test.go index f009af87871..8ba2741e63c 100644 --- a/state/applicationoffers_test.go +++ b/state/applicationoffers_test.go @@ -5,7 +5,6 @@ package state_test import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" jujutxn "github.com/juju/txn/v3" @@ -15,6 +14,7 @@ import ( "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/permission" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" statetesting "github.com/juju/juju/state/testing" "github.com/juju/juju/testing" diff --git a/state/charm.go b/state/charm.go index a9d0c7be54e..f474dbb3784 100644 --- a/state/charm.go +++ b/state/charm.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -21,6 +20,7 @@ import ( corebase "github.com/juju/juju/core/base" corecharm "github.com/juju/juju/core/charm" "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/mongo" mongoutils "github.com/juju/juju/internal/mongo/utils" stateerrors "github.com/juju/juju/state/errors" diff --git a/state/charm_test.go b/state/charm_test.go index 865601a067c..a46065094b1 100644 --- a/state/charm_test.go +++ b/state/charm_test.go @@ -11,13 +11,13 @@ import ( "strings" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v4" gc "gopkg.in/check.v1" "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" "github.com/juju/juju/testcharms" "github.com/juju/juju/testing/factory" diff --git a/state/cleanup_test.go b/state/cleanup_test.go index f88937bf7a2..e950054a463 100644 --- a/state/cleanup_test.go +++ b/state/cleanup_test.go @@ -11,7 +11,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -25,6 +24,7 @@ import ( "github.com/juju/juju/core/instance" resourcetesting "github.com/juju/juju/core/resources/testing" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" corestorage "github.com/juju/juju/internal/storage" "github.com/juju/juju/state" "github.com/juju/juju/state/stateenvirons" diff --git a/state/devices.go b/state/devices.go index 36561c15f02..ab8ad350786 100644 --- a/state/devices.go +++ b/state/devices.go @@ -7,11 +7,11 @@ import ( "context" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/txn" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" ) type DeviceType string diff --git a/state/endpoint_bindings.go b/state/endpoint_bindings.go index cb2becf15b2..3243b52d843 100644 --- a/state/endpoint_bindings.go +++ b/state/endpoint_bindings.go @@ -6,13 +6,13 @@ package state import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" jujutxn "github.com/juju/txn/v3" "github.com/juju/juju/core/network" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/mongo/utils" ) diff --git a/state/endpoint_test.go b/state/endpoint_test.go index dfca7992f4a..56109eecfdd 100644 --- a/state/endpoint_test.go +++ b/state/endpoint_test.go @@ -4,10 +4,10 @@ package state_test import ( - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" ) diff --git a/state/export_test.go b/state/export_test.go index 2e599b0d93d..b59aaefaf0a 100644 --- a/state/export_test.go +++ b/state/export_test.go @@ -14,7 +14,6 @@ import ( "github.com/juju/clock/testclock" "github.com/juju/description/v6" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -32,6 +31,7 @@ import ( "github.com/juju/juju/core/resources" "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" loggertesting "github.com/juju/juju/internal/logger/testing" "github.com/juju/juju/internal/mongo" "github.com/juju/juju/internal/mongo/utils" diff --git a/state/filesystem_test.go b/state/filesystem_test.go index 94bdd29c56a..67d7b661340 100644 --- a/state/filesystem_test.go +++ b/state/filesystem_test.go @@ -5,7 +5,6 @@ package state_test import ( "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" @@ -14,6 +13,7 @@ import ( "github.com/juju/juju/caas/kubernetes/provider" k8stesting "github.com/juju/juju/caas/kubernetes/provider/testing" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" "github.com/juju/juju/state/testing" ) diff --git a/state/machine.go b/state/machine.go index e32f271d25f..215fc83425e 100644 --- a/state/machine.go +++ b/state/machine.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -31,6 +30,7 @@ import ( "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/bootstrap" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/mongo" internalpassword "github.com/juju/juju/internal/password" "github.com/juju/juju/internal/tools" diff --git a/state/migration_export.go b/state/migration_export.go index 728a493361f..539c1234729 100644 --- a/state/migration_export.go +++ b/state/migration_export.go @@ -12,7 +12,6 @@ import ( "github.com/juju/description/v6" "github.com/juju/errors" "github.com/juju/featureflag" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" "github.com/juju/names/v5" @@ -25,6 +24,7 @@ import ( "github.com/juju/juju/core/payloads" "github.com/juju/juju/core/resources" "github.com/juju/juju/core/secrets" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/feature" internallogger "github.com/juju/juju/internal/logger" secretsprovider "github.com/juju/juju/internal/secrets/provider" diff --git a/state/migration_export_test.go b/state/migration_export_test.go index 691812bcb52..4fbddf0bee7 100644 --- a/state/migration_export_test.go +++ b/state/migration_export_test.go @@ -12,8 +12,6 @@ import ( "github.com/juju/description/v6" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -33,6 +31,8 @@ import ( "github.com/juju/juju/core/status" domainstorage "github.com/juju/juju/domain/storage" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/feature" internalpassword "github.com/juju/juju/internal/password" "github.com/juju/juju/internal/storage" diff --git a/state/migration_import.go b/state/migration_import.go index 240506939ee..0a090916ee7 100644 --- a/state/migration_import.go +++ b/state/migration_import.go @@ -14,7 +14,6 @@ import ( "github.com/juju/collections/transform" "github.com/juju/description/v6" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" "github.com/juju/names/v5" @@ -33,6 +32,7 @@ import ( "github.com/juju/juju/core/status" applicationservice "github.com/juju/juju/domain/application/service" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" secretsprovider "github.com/juju/juju/internal/secrets/provider" "github.com/juju/juju/internal/storage" diff --git a/state/migration_import_test.go b/state/migration_import_test.go index fcde930f442..1fd1a344566 100644 --- a/state/migration_import_test.go +++ b/state/migration_import_test.go @@ -11,7 +11,6 @@ import ( "github.com/juju/description/v6" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -29,6 +28,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" internalpassword "github.com/juju/juju/internal/password" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/uuid" diff --git a/state/migration_internal_test.go b/state/migration_internal_test.go index dff7a565af6..e9b0c3630e7 100644 --- a/state/migration_internal_test.go +++ b/state/migration_internal_test.go @@ -5,9 +5,9 @@ package state import ( "github.com/juju/collections/set" - "github.com/juju/juju/internal/charm" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/testing" ) diff --git a/state/migrations/remoteapplications.go b/state/migrations/remoteapplications.go index 1e00bc6dcde..d5317cc953f 100644 --- a/state/migrations/remoteapplications.go +++ b/state/migrations/remoteapplications.go @@ -6,8 +6,9 @@ package migrations import ( "github.com/juju/description/v6" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" + + "github.com/juju/juju/internal/charm" ) // MigrationRemoteApplication is an in-place representation of the diff --git a/state/model_test.go b/state/model_test.go index 07b6fdc3442..471f9616b5c 100644 --- a/state/model_test.go +++ b/state/model_test.go @@ -11,7 +11,6 @@ import ( "github.com/juju/clock" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" mgotesting "github.com/juju/mgo/v3/testing" "github.com/juju/names/v5" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/cloud" "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/uuid" "github.com/juju/juju/state" diff --git a/state/modelgeneration.go b/state/modelgeneration.go index 0fa3a77d1c1..dcd29cad744 100644 --- a/state/modelgeneration.go +++ b/state/modelgeneration.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -19,6 +18,7 @@ import ( jujutxn "github.com/juju/txn/v3" "github.com/juju/juju/core/settings" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/mongo/utils" stateerrors "github.com/juju/juju/state/errors" ) diff --git a/state/modelgeneration_test.go b/state/modelgeneration_test.go index 4dc79aecadd..e17a17f9b2a 100644 --- a/state/modelgeneration_test.go +++ b/state/modelgeneration_test.go @@ -8,12 +8,12 @@ import ( "github.com/juju/clock/testclock" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/model" "github.com/juju/juju/core/settings" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" "github.com/juju/juju/testing" ) diff --git a/state/payloads_ns.go b/state/payloads_ns.go index 5e120ead171..5178e9ed5f2 100644 --- a/state/payloads_ns.go +++ b/state/payloads_ns.go @@ -7,11 +7,11 @@ import ( "fmt" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/mongo" ) diff --git a/state/payloads_test.go b/state/payloads_test.go index 337a1df8997..0c896a4721f 100644 --- a/state/payloads_test.go +++ b/state/payloads_test.go @@ -9,11 +9,11 @@ import ( "sort" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/payloads" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" "github.com/juju/juju/testing/factory" ) diff --git a/state/relation.go b/state/relation.go index 07ce55abace..3c751d8edd8 100644 --- a/state/relation.go +++ b/state/relation.go @@ -11,7 +11,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/core/leadership" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" ) // relationKey returns a string describing the relation defined by diff --git a/state/relation_internal_test.go b/state/relation_internal_test.go index 386058f65d0..1224e6c4f2b 100644 --- a/state/relation_internal_test.go +++ b/state/relation_internal_test.go @@ -4,9 +4,10 @@ package state import ( - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + + "github.com/juju/juju/internal/charm" ) type RelationSuite struct{} diff --git a/state/relation_test.go b/state/relation_test.go index 08137ae9c85..072d9e8ee9a 100644 --- a/state/relation_test.go +++ b/state/relation_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" @@ -19,6 +18,7 @@ import ( "github.com/juju/juju/core/permission" "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/uuid" "github.com/juju/juju/state" "github.com/juju/juju/state/testing" diff --git a/state/relationunit.go b/state/relationunit.go index 8dde66837ed..3da25de8817 100644 --- a/state/relationunit.go +++ b/state/relationunit.go @@ -10,7 +10,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -19,6 +18,7 @@ import ( "github.com/kr/pretty" corelogger "github.com/juju/juju/core/logger" + "github.com/juju/juju/internal/charm" stateerrors "github.com/juju/juju/state/errors" ) diff --git a/state/relationunit_test.go b/state/relationunit_test.go index 2fc4540279d..2c411e31513 100644 --- a/state/relationunit_test.go +++ b/state/relationunit_test.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" @@ -20,6 +19,7 @@ import ( "github.com/juju/juju/core/network" "github.com/juju/juju/core/objectstore" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" stateerrors "github.com/juju/juju/state/errors" "github.com/juju/juju/state/testing" diff --git a/state/remoteapplication.go b/state/remoteapplication.go index 955408217ff..8306b683395 100644 --- a/state/remoteapplication.go +++ b/state/remoteapplication.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -21,6 +20,7 @@ import ( "github.com/juju/juju/core/crossmodel" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" ) // RemoteApplication represents the state of an application hosted diff --git a/state/remoteapplication_test.go b/state/remoteapplication_test.go index dc71cf48d4a..55e374f8a40 100644 --- a/state/remoteapplication_test.go +++ b/state/remoteapplication_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" @@ -16,6 +15,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/uuid" "github.com/juju/juju/state" "github.com/juju/juju/state/testing" diff --git a/state/resources.go b/state/resources.go index ca6167baf9b..5407c5b9b5b 100644 --- a/state/resources.go +++ b/state/resources.go @@ -15,7 +15,6 @@ import ( "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -25,6 +24,7 @@ import ( corelogger "github.com/juju/juju/core/logger" "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/resources" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/internal/docker" "github.com/juju/juju/internal/uuid" ) diff --git a/state/resources_staged_test.go b/state/resources_staged_test.go index 0f4479f6c9f..9b8fda2b1bd 100644 --- a/state/resources_staged_test.go +++ b/state/resources_staged_test.go @@ -8,10 +8,10 @@ import ( "crypto/sha512" "fmt" - charmresource "github.com/juju/juju/internal/charm/resource" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/state" "github.com/juju/juju/testing/factory" ) diff --git a/state/resources_test.go b/state/resources_test.go index 57a0d514c36..922fac6043c 100644 --- a/state/resources_test.go +++ b/state/resources_test.go @@ -12,14 +12,14 @@ import ( "time" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/core/resources" resourcetesting "github.com/juju/juju/core/resources/testing" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/juju/state" "github.com/juju/juju/testing" "github.com/juju/juju/testing/factory" diff --git a/state/secrets_test.go b/state/secrets_test.go index 469bd7e57ee..60592da4d2b 100644 --- a/state/secrets_test.go +++ b/state/secrets_test.go @@ -10,7 +10,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/worker/v4/workertest" @@ -18,6 +17,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/storage/provider/dummy" "github.com/juju/juju/internal/uuid" diff --git a/state/state.go b/state/state.go index ee0ce1ba470..e74ef431745 100644 --- a/state/state.go +++ b/state/state.go @@ -16,7 +16,6 @@ import ( "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -40,6 +39,7 @@ import ( "github.com/juju/juju/environs" environsconfig "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/envcontext" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/internal/mongo" "github.com/juju/juju/internal/storage" diff --git a/state/state_test.go b/state/state_test.go index e1b915b26e2..996dbf4d925 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -15,7 +15,6 @@ import ( "github.com/juju/clock" "github.com/juju/clock/testclock" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" @@ -45,6 +44,7 @@ import ( "github.com/juju/juju/core/upgrade" domainstorage "github.com/juju/juju/domain/storage" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/mongo" "github.com/juju/juju/internal/mongo/mongotest" "github.com/juju/juju/internal/uuid" diff --git a/state/storage.go b/state/storage.go index 1465fed8bef..b60bfbe4003 100644 --- a/state/storage.go +++ b/state/storage.go @@ -12,7 +12,6 @@ import ( "github.com/dustin/go-humanize" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -22,6 +21,7 @@ import ( k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" storageerrors "github.com/juju/juju/domain/storage/errors" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/storage/provider" stateerrors "github.com/juju/juju/state/errors" diff --git a/state/storage_test.go b/state/storage_test.go index c499e42b228..b97b36e5c7c 100644 --- a/state/storage_test.go +++ b/state/storage_test.go @@ -8,7 +8,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" @@ -20,6 +19,7 @@ import ( k8stesting "github.com/juju/juju/caas/kubernetes/provider/testing" "github.com/juju/juju/cloud" domainstorage "github.com/juju/juju/domain/storage" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/internal/storage" "github.com/juju/juju/internal/storage/provider" dummystorage "github.com/juju/juju/internal/storage/provider/dummy" diff --git a/state/unit.go b/state/unit.go index ddfc0b75f70..ffb7d1ad1c1 100644 --- a/state/unit.go +++ b/state/unit.go @@ -11,7 +11,6 @@ import ( "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/mgo/v3/txn" @@ -27,6 +26,7 @@ import ( "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/status" "github.com/juju/juju/environs" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" mgoutils "github.com/juju/juju/internal/mongo/utils" internalpassword "github.com/juju/juju/internal/password" diff --git a/state/unit_test.go b/state/unit_test.go index 553d18fd848..1cdaa2efb41 100644 --- a/state/unit_test.go +++ b/state/unit_test.go @@ -11,7 +11,6 @@ import ( "time" // Only used for time types. "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/loggo/v2" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" @@ -27,6 +26,7 @@ import ( "github.com/juju/juju/core/network" "github.com/juju/juju/core/secrets" "github.com/juju/juju/core/status" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/state" stateerrors "github.com/juju/juju/state/errors" "github.com/juju/juju/state/testing" diff --git a/state/watcher.go b/state/watcher.go index ba32366cc63..c61cdc52459 100644 --- a/state/watcher.go +++ b/state/watcher.go @@ -17,7 +17,6 @@ import ( "github.com/juju/clock" "github.com/juju/collections/set" "github.com/juju/errors" - "github.com/juju/juju/internal/charm" "github.com/juju/mgo/v3" "github.com/juju/mgo/v3/bson" "github.com/juju/names/v5" @@ -30,6 +29,7 @@ import ( "github.com/juju/juju/core/lxdprofile" corenetwork "github.com/juju/juju/core/network" corewatcher "github.com/juju/juju/core/watcher" + "github.com/juju/juju/internal/charm" internallogger "github.com/juju/juju/internal/logger" "github.com/juju/juju/internal/mongo" "github.com/juju/juju/state/watcher" diff --git a/testcharms/charm.go b/testcharms/charm.go index cf3c91c1d2e..031af7f67b4 100644 --- a/testcharms/charm.go +++ b/testcharms/charm.go @@ -10,10 +10,10 @@ import ( "os" "time" - "github.com/juju/juju/internal/charm" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" + "github.com/juju/juju/internal/charm" "github.com/juju/juju/testcharms/repo" jtesting "github.com/juju/juju/testing" ) diff --git a/testcharms/repo/repo.go b/testcharms/repo/repo.go index 4adb9af05c0..0708fafae11 100644 --- a/testcharms/repo/repo.go +++ b/testcharms/repo/repo.go @@ -13,8 +13,9 @@ import ( "path/filepath" "runtime" - "github.com/juju/juju/internal/charm" "github.com/juju/utils/v4/fs" + + "github.com/juju/juju/internal/charm" ) func check(err error) { diff --git a/testing/factory/factory.go b/testing/factory/factory.go index 645d58b6cbf..eab93bb7d0b 100644 --- a/testing/factory/factory.go +++ b/testing/factory/factory.go @@ -10,8 +10,6 @@ import ( "sync/atomic" "time" - "github.com/juju/juju/internal/charm" - charmresource "github.com/juju/juju/internal/charm/resource" "github.com/juju/names/v5" jc "github.com/juju/testing/checkers" "github.com/juju/version/v2" @@ -30,6 +28,8 @@ import ( applicationservice "github.com/juju/juju/domain/application/service" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" + "github.com/juju/juju/internal/charm" + charmresource "github.com/juju/juju/internal/charm/resource" loggertesting "github.com/juju/juju/internal/logger/testing" internalobjectstore "github.com/juju/juju/internal/objectstore" objectstoretesting "github.com/juju/juju/internal/objectstore/testing" diff --git a/tools.go b/tools.go index 67a725c7724..f59e26525cc 100644 --- a/tools.go +++ b/tools.go @@ -6,9 +6,8 @@ package juju import ( - // go.uber.org/mock/mockgen for generating mocks - _ "go.uber.org/mock/mockgen" - // github.com/canonical/pebble/cmd/pebble for pebble binary generation _ "github.com/canonical/pebble/cmd/pebble" + // go.uber.org/mock/mockgen for generating mocks + _ "go.uber.org/mock/mockgen" ) From a4cb209f311b3b3894a2967e2ffbab5207e13b53 Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Tue, 21 May 2024 16:59:12 +0100 Subject: [PATCH 5/9] Fix typos in the charm package --- internal/charm/charmarchive_test.go | 5 ++--- internal/charm/charmdir_test.go | 3 +-- internal/charm/meta.go | 4 ++-- internal/charm/meta_test.go | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/internal/charm/charmarchive_test.go b/internal/charm/charmarchive_test.go index e1ff295e901..113a124813e 100644 --- a/internal/charm/charmarchive_test.go +++ b/internal/charm/charmarchive_test.go @@ -292,11 +292,10 @@ func (s *CharmArchiveSuite) TestExpandToSetsHooksExecutable(c *gc.C) { c.Assert(err, jc.ErrorIsNil) for name := range archive.Meta().Hooks() { - hookName := string(name) - info, err := os.Stat(filepath.Join(path, "hooks", hookName)) + info, err := os.Stat(filepath.Join(path, "hooks", name)) c.Assert(err, jc.ErrorIsNil) perm := info.Mode() & 0777 - c.Assert(perm&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", hookName)) + c.Assert(perm&0100 != 0, gc.Equals, true, gc.Commentf("hook %q is not executable", name)) } } diff --git a/internal/charm/charmdir_test.go b/internal/charm/charmdir_test.go index 420e8c161fd..7634d4bdcd9 100644 --- a/internal/charm/charmdir_test.go +++ b/internal/charm/charmdir_test.go @@ -280,8 +280,7 @@ func (s *CharmSuite) TestMaybeGenerateVersionStringHasAVersionFile(c *gc.C) { version, vcsType, err := dir.MaybeGenerateVersionString() c.Assert(version, gc.Equals, expectedVersionNumber) - VersionFileType := "versionFile" - c.Assert(vcsType, gc.Equals, VersionFileType) + c.Assert(vcsType, gc.Equals, "versionFile") c.Assert(err, jc.ErrorIsNil) } diff --git a/internal/charm/meta.go b/internal/charm/meta.go index 8601b26904f..843597b3133 100644 --- a/internal/charm/meta.go +++ b/internal/charm/meta.go @@ -1221,10 +1221,10 @@ func parseMounts(input interface{}, storage map[string]Storage) ([]Mount, error) mount.Location = value } if mount.Storage == "" { - return nil, errors.Errorf("storage must be specifed on mount") + return nil, errors.Errorf("storage must be specified on mount") } if mount.Location == "" { - return nil, errors.Errorf("location must be specifed on mount") + return nil, errors.Errorf("location must be specified on mount") } if _, ok := storage[mount.Storage]; !ok { return nil, errors.NotValidf("storage %q", mount.Storage) diff --git a/internal/charm/meta_test.go b/internal/charm/meta_test.go index a9ebf6110f9..bddd629e9b4 100644 --- a/internal/charm/meta_test.go +++ b/internal/charm/meta_test.go @@ -1842,7 +1842,7 @@ storage: a: type: filesystem `)) - c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo": storage must be specifed on mount`) + c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo": storage must be specified on mount`) } func (s *MetaSuite) TestMountMissingLocation(c *gc.C) { @@ -1862,7 +1862,7 @@ storage: a: type: filesystem `)) - c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo": location must be specifed on mount`) + c.Assert(err, gc.ErrorMatches, `parsing containers: container "foo": location must be specified on mount`) } func (s *MetaSuite) TestMountIncorrectStorage(c *gc.C) { From 9d4c7b63fa15f8b63ea1b61b3c95fc4d99fd497d Mon Sep 17 00:00:00 2001 From: Jack Shaw Date: Tue, 21 May 2024 17:07:52 +0100 Subject: [PATCH 6/9] Fix comment formatting to make static analysis happy --- internal/charm/overlay.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/charm/overlay.go b/internal/charm/overlay.go index 79216a01bd9..2fc8ed06e18 100644 --- a/internal/charm/overlay.go +++ b/internal/charm/overlay.go @@ -372,13 +372,18 @@ func isZero(v reflect.Value) bool { // application options and annotations. // // When merging an overlay into a base bundle the following rules apply for the -// BundleData struct fields: +// BundleData fields: +// // - if an overlay specifies a bundle-level series, it overrides the base bundle // series. +// // - overlay-defined relations are appended to the base bundle relations +// // - overlay-defined machines overwrite the base bundle machines. +// // - if an overlay defines an application that is not present in the base bundle, // it will get appended to the application list. +// // - if an overlay defines an empty application or saas value, it will be removed // from the base bundle together with any associated relations. For example, to // remove an application named "mysql" the following overlay snippet can be @@ -390,14 +395,19 @@ func isZero(v reflect.Value) bool { // the two application specs are merged together (see following rules) // // ApplicationSpec merge rules: +// // - if the overlay defines a value for a scalar or slice field, it will overwrite // the value from the base spec (e.g. trust, series etc). +// // - if the overlay specifies a nil/empty value for a map field, then the map // field of the base spec will be cleared. +// // - if the overlay specifies a non-empty value for a map field, its key/value // tuples are iterated and: +// // - if the value is nil/zero and the value is non-scalar, it is deleted from // the base spec. +// // - otherwise, the key/value is inserted into the base spec overwriting any // existing entries. func ReadAndMergeBundleData(sources ...BundleDataSource) (*BundleData, error) { From e7b60648e6e9843046c6d6cc032a86039b925713 Mon Sep 17 00:00:00 2001 From: Jack Shaw Date: Tue, 21 May 2024 17:17:31 +0100 Subject: [PATCH 7/9] Check returned error for AddFile in charm --- internal/charm/charmdir.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/charm/charmdir.go b/internal/charm/charmdir.go index 654a80bfe7e..e5d3b9737c0 100644 --- a/internal/charm/charmdir.go +++ b/internal/charm/charmdir.go @@ -291,10 +291,16 @@ func writeArchive( logger: logger, } if revision != -1 { - zp.AddFile("revision", strconv.Itoa(revision)) + err := zp.AddFile("revision", strconv.Itoa(revision)) + if err != nil { + return errors.Annotatef(err, "adding 'revision' file") + } } if versionString != "" { - zp.AddFile("version", versionString) + err := zp.AddFile("version", versionString) + if err != nil { + return errors.Annotatef(err, "adding 'version' file") + } } if err := filepath.Walk(rootPath, zp.WalkFunc()); err != nil { return errors.Annotatef(err, "walking charm directory") From 8a40d41092d1bcf3a6e4d86fc99d2945b510589d Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Tue, 21 May 2024 17:36:40 +0100 Subject: [PATCH 8/9] Rebuild mocks for charm packages. --- .../deployrepository_mocks_test.go | 2 +- .../application/mocks/application_mock.go | 4 +- .../application/mocks/lxdprofile_mock.go | 4 +- .../client/application/package_test.go | 2 +- .../application/repository_mocks_test.go | 82 +++++++++---------- .../application/updatebase_mocks_test.go | 2 +- .../application/deployer/mocks/charm_mock.go | 4 +- .../application/deployer/mocks/deploy_mock.go | 62 +++++++------- .../deployer/mocks/resolver_mock.go | 38 ++++----- cmd/juju/application/deployer/package_test.go | 2 +- .../application/refresher/charm_mock_test.go | 4 +- .../application/refresher/package_test.go | 2 +- .../refresher/refresher_mock_test.go | 28 +++---- .../application/refresher/store_mock_test.go | 30 +++---- .../application/store/mocks/charm_mock.go | 4 +- .../application/store/mocks/store_mock.go | 46 +++++------ cmd/juju/application/store/package_test.go | 2 +- core/charm/charm_mock_test.go | 4 +- core/charm/package_test.go | 2 +- core/model/charm_mock_test.go | 4 +- core/model/package_test.go | 2 +- scripts/charmhub/locate-series-less-charms.go | 9 +- 22 files changed, 170 insertions(+), 169 deletions(-) diff --git a/apiserver/facades/client/application/deployrepository_mocks_test.go b/apiserver/facades/client/application/deployrepository_mocks_test.go index 0e3e7d83e92..0a10410305e 100644 --- a/apiserver/facades/client/application/deployrepository_mocks_test.go +++ b/apiserver/facades/client/application/deployrepository_mocks_test.go @@ -13,12 +13,12 @@ import ( context "context" reflect "reflect" - resource "github.com/juju/juju/internal/charm/resource" constraints "github.com/juju/juju/core/constraints" instance "github.com/juju/juju/core/instance" network "github.com/juju/juju/core/network" objectstore "github.com/juju/juju/core/objectstore" config "github.com/juju/juju/environs/config" + resource "github.com/juju/juju/internal/charm/resource" services "github.com/juju/juju/internal/charm/services" params "github.com/juju/juju/rpc/params" state "github.com/juju/juju/state" diff --git a/apiserver/facades/client/application/mocks/application_mock.go b/apiserver/facades/client/application/mocks/application_mock.go index 469fe6b3842..ad86f411958 100644 --- a/apiserver/facades/client/application/mocks/application_mock.go +++ b/apiserver/facades/client/application/mocks/application_mock.go @@ -14,8 +14,6 @@ import ( reflect "reflect" time "time" - charm "github.com/juju/juju/internal/charm" - resource "github.com/juju/juju/internal/charm/resource" storagecommon "github.com/juju/juju/apiserver/common/storagecommon" application "github.com/juju/juju/apiserver/facades/client/application" config "github.com/juju/juju/core/config" @@ -26,6 +24,8 @@ import ( objectstore "github.com/juju/juju/core/objectstore" status "github.com/juju/juju/core/status" config0 "github.com/juju/juju/environs/config" + charm "github.com/juju/juju/internal/charm" + resource "github.com/juju/juju/internal/charm/resource" services "github.com/juju/juju/internal/charm/services" tools "github.com/juju/juju/internal/tools" state "github.com/juju/juju/state" diff --git a/apiserver/facades/client/application/mocks/lxdprofile_mock.go b/apiserver/facades/client/application/mocks/lxdprofile_mock.go index 30aa48cbf71..d6bd05894ed 100644 --- a/apiserver/facades/client/application/mocks/lxdprofile_mock.go +++ b/apiserver/facades/client/application/mocks/lxdprofile_mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/juju/charm/v13 (interfaces: LXDProfiler) +// Source: github.com/juju/juju/internal/charm (interfaces: LXDProfiler) // // Generated by this command: // -// mockgen -typed -package mocks -destination mocks/lxdprofile_mock.go github.com/juju/charm/v13 LXDProfiler +// mockgen -typed -package mocks -destination mocks/lxdprofile_mock.go github.com/juju/juju/internal/charm LXDProfiler // // Package mocks is a generated GoMock package. diff --git a/apiserver/facades/client/application/package_test.go b/apiserver/facades/client/application/package_test.go index 2b6d9b615c5..e862baf6cef 100644 --- a/apiserver/facades/client/application/package_test.go +++ b/apiserver/facades/client/application/package_test.go @@ -13,7 +13,7 @@ func TestAll(t *stdtesting.T) { testing.MgoTestPackage(t) } -//go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/lxdprofile_mock.go github.com/juju/charm/v13 LXDProfiler +//go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/lxdprofile_mock.go github.com/juju/juju/internal/charm LXDProfiler //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/state_mock.go github.com/juju/juju/state StorageAttachment,StorageInstance,MachinePortRanges,UnitPortRanges,CloudContainer //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/storage_mock.go github.com/juju/juju/internal/storage ProviderRegistry //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/leadership_mock.go github.com/juju/juju/core/leadership Reader diff --git a/apiserver/facades/client/application/repository_mocks_test.go b/apiserver/facades/client/application/repository_mocks_test.go index 7571f7ed9a7..f2a2c499d91 100644 --- a/apiserver/facades/client/application/repository_mocks_test.go +++ b/apiserver/facades/client/application/repository_mocks_test.go @@ -14,9 +14,9 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/juju/internal/charm" + charm "github.com/juju/juju/core/charm" + charm0 "github.com/juju/juju/internal/charm" resource "github.com/juju/juju/internal/charm/resource" - charm0 "github.com/juju/juju/core/charm" gomock "go.uber.org/mock/gomock" ) @@ -44,11 +44,11 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { } // DownloadCharm mocks base method. -func (m *MockRepository) DownloadCharm(arg0 context.Context, arg1 string, arg2 charm0.Origin, arg3 string) (charm0.CharmArchive, charm0.Origin, error) { +func (m *MockRepository) DownloadCharm(arg0 context.Context, arg1 string, arg2 charm.Origin, arg3 string) (charm.CharmArchive, charm.Origin, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DownloadCharm", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(charm0.CharmArchive) - ret1, _ := ret[1].(charm0.Origin) + ret0, _ := ret[0].(charm.CharmArchive) + ret1, _ := ret[1].(charm.Origin) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -66,29 +66,29 @@ type MockRepositoryDownloadCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockRepositoryDownloadCharmCall) Return(arg0 charm0.CharmArchive, arg1 charm0.Origin, arg2 error) *MockRepositoryDownloadCharmCall { +func (c *MockRepositoryDownloadCharmCall) Return(arg0 charm.CharmArchive, arg1 charm.Origin, arg2 error) *MockRepositoryDownloadCharmCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *MockRepositoryDownloadCharmCall) Do(f func(context.Context, string, charm0.Origin, string) (charm0.CharmArchive, charm0.Origin, error)) *MockRepositoryDownloadCharmCall { +func (c *MockRepositoryDownloadCharmCall) Do(f func(context.Context, string, charm.Origin, string) (charm.CharmArchive, charm.Origin, error)) *MockRepositoryDownloadCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockRepositoryDownloadCharmCall) DoAndReturn(f func(context.Context, string, charm0.Origin, string) (charm0.CharmArchive, charm0.Origin, error)) *MockRepositoryDownloadCharmCall { +func (c *MockRepositoryDownloadCharmCall) DoAndReturn(f func(context.Context, string, charm.Origin, string) (charm.CharmArchive, charm.Origin, error)) *MockRepositoryDownloadCharmCall { c.Call = c.Call.DoAndReturn(f) return c } // GetDownloadURL mocks base method. -func (m *MockRepository) GetDownloadURL(arg0 context.Context, arg1 string, arg2 charm0.Origin) (*url.URL, charm0.Origin, error) { +func (m *MockRepository) GetDownloadURL(arg0 context.Context, arg1 string, arg2 charm.Origin) (*url.URL, charm.Origin, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDownloadURL", arg0, arg1, arg2) ret0, _ := ret[0].(*url.URL) - ret1, _ := ret[1].(charm0.Origin) + ret1, _ := ret[1].(charm.Origin) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -106,32 +106,32 @@ type MockRepositoryGetDownloadURLCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockRepositoryGetDownloadURLCall) Return(arg0 *url.URL, arg1 charm0.Origin, arg2 error) *MockRepositoryGetDownloadURLCall { +func (c *MockRepositoryGetDownloadURLCall) Return(arg0 *url.URL, arg1 charm.Origin, arg2 error) *MockRepositoryGetDownloadURLCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *MockRepositoryGetDownloadURLCall) Do(f func(context.Context, string, charm0.Origin) (*url.URL, charm0.Origin, error)) *MockRepositoryGetDownloadURLCall { +func (c *MockRepositoryGetDownloadURLCall) Do(f func(context.Context, string, charm.Origin) (*url.URL, charm.Origin, error)) *MockRepositoryGetDownloadURLCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockRepositoryGetDownloadURLCall) DoAndReturn(f func(context.Context, string, charm0.Origin) (*url.URL, charm0.Origin, error)) *MockRepositoryGetDownloadURLCall { +func (c *MockRepositoryGetDownloadURLCall) DoAndReturn(f func(context.Context, string, charm.Origin) (*url.URL, charm.Origin, error)) *MockRepositoryGetDownloadURLCall { c.Call = c.Call.DoAndReturn(f) return c } // GetEssentialMetadata mocks base method. -func (m *MockRepository) GetEssentialMetadata(arg0 context.Context, arg1 ...charm0.MetadataRequest) ([]charm0.EssentialMetadata, error) { +func (m *MockRepository) GetEssentialMetadata(arg0 context.Context, arg1 ...charm.MetadataRequest) ([]charm.EssentialMetadata, error) { m.ctrl.T.Helper() varargs := []any{arg0} for _, a := range arg1 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetEssentialMetadata", varargs...) - ret0, _ := ret[0].([]charm0.EssentialMetadata) + ret0, _ := ret[0].([]charm.EssentialMetadata) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -150,25 +150,25 @@ type MockRepositoryGetEssentialMetadataCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockRepositoryGetEssentialMetadataCall) Return(arg0 []charm0.EssentialMetadata, arg1 error) *MockRepositoryGetEssentialMetadataCall { +func (c *MockRepositoryGetEssentialMetadataCall) Return(arg0 []charm.EssentialMetadata, arg1 error) *MockRepositoryGetEssentialMetadataCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockRepositoryGetEssentialMetadataCall) Do(f func(context.Context, ...charm0.MetadataRequest) ([]charm0.EssentialMetadata, error)) *MockRepositoryGetEssentialMetadataCall { +func (c *MockRepositoryGetEssentialMetadataCall) Do(f func(context.Context, ...charm.MetadataRequest) ([]charm.EssentialMetadata, error)) *MockRepositoryGetEssentialMetadataCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockRepositoryGetEssentialMetadataCall) DoAndReturn(f func(context.Context, ...charm0.MetadataRequest) ([]charm0.EssentialMetadata, error)) *MockRepositoryGetEssentialMetadataCall { +func (c *MockRepositoryGetEssentialMetadataCall) DoAndReturn(f func(context.Context, ...charm.MetadataRequest) ([]charm.EssentialMetadata, error)) *MockRepositoryGetEssentialMetadataCall { c.Call = c.Call.DoAndReturn(f) return c } // ListResources mocks base method. -func (m *MockRepository) ListResources(arg0 context.Context, arg1 string, arg2 charm0.Origin) ([]resource.Resource, error) { +func (m *MockRepository) ListResources(arg0 context.Context, arg1 string, arg2 charm.Origin) ([]resource.Resource, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListResources", arg0, arg1, arg2) ret0, _ := ret[0].([]resource.Resource) @@ -195,22 +195,22 @@ func (c *MockRepositoryListResourcesCall) Return(arg0 []resource.Resource, arg1 } // Do rewrite *gomock.Call.Do -func (c *MockRepositoryListResourcesCall) Do(f func(context.Context, string, charm0.Origin) ([]resource.Resource, error)) *MockRepositoryListResourcesCall { +func (c *MockRepositoryListResourcesCall) Do(f func(context.Context, string, charm.Origin) ([]resource.Resource, error)) *MockRepositoryListResourcesCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockRepositoryListResourcesCall) DoAndReturn(f func(context.Context, string, charm0.Origin) ([]resource.Resource, error)) *MockRepositoryListResourcesCall { +func (c *MockRepositoryListResourcesCall) DoAndReturn(f func(context.Context, string, charm.Origin) ([]resource.Resource, error)) *MockRepositoryListResourcesCall { c.Call = c.Call.DoAndReturn(f) return c } // ResolveForDeploy mocks base method. -func (m *MockRepository) ResolveForDeploy(arg0 context.Context, arg1 charm0.CharmID) (charm0.ResolvedDataForDeploy, error) { +func (m *MockRepository) ResolveForDeploy(arg0 context.Context, arg1 charm.CharmID) (charm.ResolvedDataForDeploy, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResolveForDeploy", arg0, arg1) - ret0, _ := ret[0].(charm0.ResolvedDataForDeploy) + ret0, _ := ret[0].(charm.ResolvedDataForDeploy) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -228,25 +228,25 @@ type MockRepositoryResolveForDeployCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockRepositoryResolveForDeployCall) Return(arg0 charm0.ResolvedDataForDeploy, arg1 error) *MockRepositoryResolveForDeployCall { +func (c *MockRepositoryResolveForDeployCall) Return(arg0 charm.ResolvedDataForDeploy, arg1 error) *MockRepositoryResolveForDeployCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockRepositoryResolveForDeployCall) Do(f func(context.Context, charm0.CharmID) (charm0.ResolvedDataForDeploy, error)) *MockRepositoryResolveForDeployCall { +func (c *MockRepositoryResolveForDeployCall) Do(f func(context.Context, charm.CharmID) (charm.ResolvedDataForDeploy, error)) *MockRepositoryResolveForDeployCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockRepositoryResolveForDeployCall) DoAndReturn(f func(context.Context, charm0.CharmID) (charm0.ResolvedDataForDeploy, error)) *MockRepositoryResolveForDeployCall { +func (c *MockRepositoryResolveForDeployCall) DoAndReturn(f func(context.Context, charm.CharmID) (charm.ResolvedDataForDeploy, error)) *MockRepositoryResolveForDeployCall { c.Call = c.Call.DoAndReturn(f) return c } // ResolveResources mocks base method. -func (m *MockRepository) ResolveResources(arg0 context.Context, arg1 []resource.Resource, arg2 charm0.CharmID) ([]resource.Resource, error) { +func (m *MockRepository) ResolveResources(arg0 context.Context, arg1 []resource.Resource, arg2 charm.CharmID) ([]resource.Resource, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResolveResources", arg0, arg1, arg2) ret0, _ := ret[0].([]resource.Resource) @@ -273,24 +273,24 @@ func (c *MockRepositoryResolveResourcesCall) Return(arg0 []resource.Resource, ar } // Do rewrite *gomock.Call.Do -func (c *MockRepositoryResolveResourcesCall) Do(f func(context.Context, []resource.Resource, charm0.CharmID) ([]resource.Resource, error)) *MockRepositoryResolveResourcesCall { +func (c *MockRepositoryResolveResourcesCall) Do(f func(context.Context, []resource.Resource, charm.CharmID) ([]resource.Resource, error)) *MockRepositoryResolveResourcesCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockRepositoryResolveResourcesCall) DoAndReturn(f func(context.Context, []resource.Resource, charm0.CharmID) ([]resource.Resource, error)) *MockRepositoryResolveResourcesCall { +func (c *MockRepositoryResolveResourcesCall) DoAndReturn(f func(context.Context, []resource.Resource, charm.CharmID) ([]resource.Resource, error)) *MockRepositoryResolveResourcesCall { c.Call = c.Call.DoAndReturn(f) return c } // ResolveWithPreferredChannel mocks base method. -func (m *MockRepository) ResolveWithPreferredChannel(arg0 context.Context, arg1 string, arg2 charm0.Origin) (*charm.URL, charm0.Origin, []charm0.Platform, error) { +func (m *MockRepository) ResolveWithPreferredChannel(arg0 context.Context, arg1 string, arg2 charm.Origin) (*charm0.URL, charm.Origin, []charm.Platform, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResolveWithPreferredChannel", arg0, arg1, arg2) - ret0, _ := ret[0].(*charm.URL) - ret1, _ := ret[1].(charm0.Origin) - ret2, _ := ret[2].([]charm0.Platform) + ret0, _ := ret[0].(*charm0.URL) + ret1, _ := ret[1].(charm.Origin) + ret2, _ := ret[2].([]charm.Platform) ret3, _ := ret[3].(error) return ret0, ret1, ret2, ret3 } @@ -308,19 +308,19 @@ type MockRepositoryResolveWithPreferredChannelCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockRepositoryResolveWithPreferredChannelCall) Return(arg0 *charm.URL, arg1 charm0.Origin, arg2 []charm0.Platform, arg3 error) *MockRepositoryResolveWithPreferredChannelCall { +func (c *MockRepositoryResolveWithPreferredChannelCall) Return(arg0 *charm0.URL, arg1 charm.Origin, arg2 []charm.Platform, arg3 error) *MockRepositoryResolveWithPreferredChannelCall { c.Call = c.Call.Return(arg0, arg1, arg2, arg3) return c } // Do rewrite *gomock.Call.Do -func (c *MockRepositoryResolveWithPreferredChannelCall) Do(f func(context.Context, string, charm0.Origin) (*charm.URL, charm0.Origin, []charm0.Platform, error)) *MockRepositoryResolveWithPreferredChannelCall { +func (c *MockRepositoryResolveWithPreferredChannelCall) Do(f func(context.Context, string, charm.Origin) (*charm0.URL, charm.Origin, []charm.Platform, error)) *MockRepositoryResolveWithPreferredChannelCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockRepositoryResolveWithPreferredChannelCall) DoAndReturn(f func(context.Context, string, charm0.Origin) (*charm.URL, charm0.Origin, []charm0.Platform, error)) *MockRepositoryResolveWithPreferredChannelCall { +func (c *MockRepositoryResolveWithPreferredChannelCall) DoAndReturn(f func(context.Context, string, charm.Origin) (*charm0.URL, charm.Origin, []charm.Platform, error)) *MockRepositoryResolveWithPreferredChannelCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -349,10 +349,10 @@ func (m *MockRepositoryFactory) EXPECT() *MockRepositoryFactoryMockRecorder { } // GetCharmRepository mocks base method. -func (m *MockRepositoryFactory) GetCharmRepository(arg0 context.Context, arg1 charm0.Source) (charm0.Repository, error) { +func (m *MockRepositoryFactory) GetCharmRepository(arg0 context.Context, arg1 charm.Source) (charm.Repository, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCharmRepository", arg0, arg1) - ret0, _ := ret[0].(charm0.Repository) + ret0, _ := ret[0].(charm.Repository) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -370,19 +370,19 @@ type MockRepositoryFactoryGetCharmRepositoryCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockRepositoryFactoryGetCharmRepositoryCall) Return(arg0 charm0.Repository, arg1 error) *MockRepositoryFactoryGetCharmRepositoryCall { +func (c *MockRepositoryFactoryGetCharmRepositoryCall) Return(arg0 charm.Repository, arg1 error) *MockRepositoryFactoryGetCharmRepositoryCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockRepositoryFactoryGetCharmRepositoryCall) Do(f func(context.Context, charm0.Source) (charm0.Repository, error)) *MockRepositoryFactoryGetCharmRepositoryCall { +func (c *MockRepositoryFactoryGetCharmRepositoryCall) Do(f func(context.Context, charm.Source) (charm.Repository, error)) *MockRepositoryFactoryGetCharmRepositoryCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockRepositoryFactoryGetCharmRepositoryCall) DoAndReturn(f func(context.Context, charm0.Source) (charm0.Repository, error)) *MockRepositoryFactoryGetCharmRepositoryCall { +func (c *MockRepositoryFactoryGetCharmRepositoryCall) DoAndReturn(f func(context.Context, charm.Source) (charm.Repository, error)) *MockRepositoryFactoryGetCharmRepositoryCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/apiserver/facades/client/application/updatebase_mocks_test.go b/apiserver/facades/client/application/updatebase_mocks_test.go index 8b01b080ad8..ddd340e6d78 100644 --- a/apiserver/facades/client/application/updatebase_mocks_test.go +++ b/apiserver/facades/client/application/updatebase_mocks_test.go @@ -13,11 +13,11 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/juju/internal/charm" base "github.com/juju/juju/core/base" config "github.com/juju/juju/core/config" constraints "github.com/juju/juju/core/constraints" objectstore "github.com/juju/juju/core/objectstore" + charm "github.com/juju/juju/internal/charm" charmhub "github.com/juju/juju/internal/charmhub" transport "github.com/juju/juju/internal/charmhub/transport" tools "github.com/juju/juju/internal/tools" diff --git a/cmd/juju/application/deployer/mocks/charm_mock.go b/cmd/juju/application/deployer/mocks/charm_mock.go index 5d6d8c6886f..536ba36ea25 100644 --- a/cmd/juju/application/deployer/mocks/charm_mock.go +++ b/cmd/juju/application/deployer/mocks/charm_mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/juju/charm/v13 (interfaces: Charm,Bundle) +// Source: github.com/juju/juju/internal/charm (interfaces: Charm,Bundle) // // Generated by this command: // -// mockgen -typed -package mocks -destination mocks/charm_mock.go github.com/juju/charm/v13 Charm,Bundle +// mockgen -typed -package mocks -destination mocks/charm_mock.go github.com/juju/juju/internal/charm Charm,Bundle // // Package mocks is a generated GoMock package. diff --git a/cmd/juju/application/deployer/mocks/deploy_mock.go b/cmd/juju/application/deployer/mocks/deploy_mock.go index 64dafc0a3d3..38f795e51a2 100644 --- a/cmd/juju/application/deployer/mocks/deploy_mock.go +++ b/cmd/juju/application/deployer/mocks/deploy_mock.go @@ -15,17 +15,17 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/juju/internal/charm" - resource "github.com/juju/juju/internal/charm/resource" cmd "github.com/juju/cmd/v4" api "github.com/juju/juju/api" base "github.com/juju/juju/api/base" application "github.com/juju/juju/api/client/application" client "github.com/juju/juju/api/client/client" - charm0 "github.com/juju/juju/api/common/charm" + charm "github.com/juju/juju/api/common/charm" charms "github.com/juju/juju/api/common/charms" constraints "github.com/juju/juju/core/constraints" crossmodel "github.com/juju/juju/core/crossmodel" + charm0 "github.com/juju/juju/internal/charm" + resource "github.com/juju/juju/internal/charm/resource" params "github.com/juju/juju/rpc/params" names "github.com/juju/names/v5" gomock "go.uber.org/mock/gomock" @@ -94,10 +94,10 @@ func (c *MockDeployerAPIAPICallCall) DoAndReturn(f func(context.Context, string, } // AddCharm mocks base method. -func (m *MockDeployerAPI) AddCharm(arg0 *charm.URL, arg1 charm0.Origin, arg2 bool) (charm0.Origin, error) { +func (m *MockDeployerAPI) AddCharm(arg0 *charm0.URL, arg1 charm.Origin, arg2 bool) (charm.Origin, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddCharm", arg0, arg1, arg2) - ret0, _ := ret[0].(charm0.Origin) + ret0, _ := ret[0].(charm.Origin) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -115,28 +115,28 @@ type MockDeployerAPIAddCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockDeployerAPIAddCharmCall) Return(arg0 charm0.Origin, arg1 error) *MockDeployerAPIAddCharmCall { +func (c *MockDeployerAPIAddCharmCall) Return(arg0 charm.Origin, arg1 error) *MockDeployerAPIAddCharmCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockDeployerAPIAddCharmCall) Do(f func(*charm.URL, charm0.Origin, bool) (charm0.Origin, error)) *MockDeployerAPIAddCharmCall { +func (c *MockDeployerAPIAddCharmCall) Do(f func(*charm0.URL, charm.Origin, bool) (charm.Origin, error)) *MockDeployerAPIAddCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockDeployerAPIAddCharmCall) DoAndReturn(f func(*charm.URL, charm0.Origin, bool) (charm0.Origin, error)) *MockDeployerAPIAddCharmCall { +func (c *MockDeployerAPIAddCharmCall) DoAndReturn(f func(*charm0.URL, charm.Origin, bool) (charm.Origin, error)) *MockDeployerAPIAddCharmCall { c.Call = c.Call.DoAndReturn(f) return c } // AddLocalCharm mocks base method. -func (m *MockDeployerAPI) AddLocalCharm(arg0 *charm.URL, arg1 charm.Charm, arg2 bool) (*charm.URL, error) { +func (m *MockDeployerAPI) AddLocalCharm(arg0 *charm0.URL, arg1 charm0.Charm, arg2 bool) (*charm0.URL, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddLocalCharm", arg0, arg1, arg2) - ret0, _ := ret[0].(*charm.URL) + ret0, _ := ret[0].(*charm0.URL) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -154,19 +154,19 @@ type MockDeployerAPIAddLocalCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockDeployerAPIAddLocalCharmCall) Return(arg0 *charm.URL, arg1 error) *MockDeployerAPIAddLocalCharmCall { +func (c *MockDeployerAPIAddLocalCharmCall) Return(arg0 *charm0.URL, arg1 error) *MockDeployerAPIAddLocalCharmCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockDeployerAPIAddLocalCharmCall) Do(f func(*charm.URL, charm.Charm, bool) (*charm.URL, error)) *MockDeployerAPIAddLocalCharmCall { +func (c *MockDeployerAPIAddLocalCharmCall) Do(f func(*charm0.URL, charm0.Charm, bool) (*charm0.URL, error)) *MockDeployerAPIAddLocalCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockDeployerAPIAddLocalCharmCall) DoAndReturn(f func(*charm.URL, charm.Charm, bool) (*charm.URL, error)) *MockDeployerAPIAddLocalCharmCall { +func (c *MockDeployerAPIAddLocalCharmCall) DoAndReturn(f func(*charm0.URL, charm0.Charm, bool) (*charm0.URL, error)) *MockDeployerAPIAddLocalCharmCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -443,7 +443,7 @@ func (c *MockDeployerAPICharmInfoCall) DoAndReturn(f func(string) (*charms.Charm } // CheckCharmPlacement mocks base method. -func (m *MockDeployerAPI) CheckCharmPlacement(arg0 string, arg1 *charm.URL) error { +func (m *MockDeployerAPI) CheckCharmPlacement(arg0 string, arg1 *charm0.URL) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CheckCharmPlacement", arg0, arg1) ret0, _ := ret[0].(error) @@ -469,13 +469,13 @@ func (c *MockDeployerAPICheckCharmPlacementCall) Return(arg0 error) *MockDeploye } // Do rewrite *gomock.Call.Do -func (c *MockDeployerAPICheckCharmPlacementCall) Do(f func(string, *charm.URL) error) *MockDeployerAPICheckCharmPlacementCall { +func (c *MockDeployerAPICheckCharmPlacementCall) Do(f func(string, *charm0.URL) error) *MockDeployerAPICheckCharmPlacementCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockDeployerAPICheckCharmPlacementCall) DoAndReturn(f func(string, *charm.URL) error) *MockDeployerAPICheckCharmPlacementCall { +func (c *MockDeployerAPICheckCharmPlacementCall) DoAndReturn(f func(string, *charm0.URL) error) *MockDeployerAPICheckCharmPlacementCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -791,11 +791,11 @@ func (c *MockDeployerAPIGetAnnotationsCall) DoAndReturn(f func([]string) ([]para } // GetCharmURLOrigin mocks base method. -func (m *MockDeployerAPI) GetCharmURLOrigin(arg0, arg1 string) (*charm.URL, charm0.Origin, error) { +func (m *MockDeployerAPI) GetCharmURLOrigin(arg0, arg1 string) (*charm0.URL, charm.Origin, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCharmURLOrigin", arg0, arg1) - ret0, _ := ret[0].(*charm.URL) - ret1, _ := ret[1].(charm0.Origin) + ret0, _ := ret[0].(*charm0.URL) + ret1, _ := ret[1].(charm.Origin) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -813,19 +813,19 @@ type MockDeployerAPIGetCharmURLOriginCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockDeployerAPIGetCharmURLOriginCall) Return(arg0 *charm.URL, arg1 charm0.Origin, arg2 error) *MockDeployerAPIGetCharmURLOriginCall { +func (c *MockDeployerAPIGetCharmURLOriginCall) Return(arg0 *charm0.URL, arg1 charm.Origin, arg2 error) *MockDeployerAPIGetCharmURLOriginCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *MockDeployerAPIGetCharmURLOriginCall) Do(f func(string, string) (*charm.URL, charm0.Origin, error)) *MockDeployerAPIGetCharmURLOriginCall { +func (c *MockDeployerAPIGetCharmURLOriginCall) Do(f func(string, string) (*charm0.URL, charm.Origin, error)) *MockDeployerAPIGetCharmURLOriginCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockDeployerAPIGetCharmURLOriginCall) DoAndReturn(f func(string, string) (*charm.URL, charm0.Origin, error)) *MockDeployerAPIGetCharmURLOriginCall { +func (c *MockDeployerAPIGetCharmURLOriginCall) DoAndReturn(f func(string, string) (*charm0.URL, charm.Origin, error)) *MockDeployerAPIGetCharmURLOriginCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1039,7 +1039,7 @@ func (c *MockDeployerAPIHTTPClientCall) DoAndReturn(f func() (*httprequest.Clien } // ListCharmResources mocks base method. -func (m *MockDeployerAPI) ListCharmResources(arg0 string, arg1 charm0.Origin) ([]resource.Resource, error) { +func (m *MockDeployerAPI) ListCharmResources(arg0 string, arg1 charm.Origin) ([]resource.Resource, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListCharmResources", arg0, arg1) ret0, _ := ret[0].([]resource.Resource) @@ -1066,13 +1066,13 @@ func (c *MockDeployerAPIListCharmResourcesCall) Return(arg0 []resource.Resource, } // Do rewrite *gomock.Call.Do -func (c *MockDeployerAPIListCharmResourcesCall) Do(f func(string, charm0.Origin) ([]resource.Resource, error)) *MockDeployerAPIListCharmResourcesCall { +func (c *MockDeployerAPIListCharmResourcesCall) Do(f func(string, charm.Origin) ([]resource.Resource, error)) *MockDeployerAPIListCharmResourcesCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockDeployerAPIListCharmResourcesCall) DoAndReturn(f func(string, charm0.Origin) ([]resource.Resource, error)) *MockDeployerAPIListCharmResourcesCall { +func (c *MockDeployerAPIListCharmResourcesCall) DoAndReturn(f func(string, charm.Origin) ([]resource.Resource, error)) *MockDeployerAPIListCharmResourcesCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1644,11 +1644,11 @@ func (m *MockCharmReader) EXPECT() *MockCharmReaderMockRecorder { } // NewCharmAtPath mocks base method. -func (m *MockCharmReader) NewCharmAtPath(arg0 string) (charm.Charm, *charm.URL, error) { +func (m *MockCharmReader) NewCharmAtPath(arg0 string) (charm0.Charm, *charm0.URL, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewCharmAtPath", arg0) - ret0, _ := ret[0].(charm.Charm) - ret1, _ := ret[1].(*charm.URL) + ret0, _ := ret[0].(charm0.Charm) + ret1, _ := ret[1].(*charm0.URL) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -1666,19 +1666,19 @@ type MockCharmReaderNewCharmAtPathCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockCharmReaderNewCharmAtPathCall) Return(arg0 charm.Charm, arg1 *charm.URL, arg2 error) *MockCharmReaderNewCharmAtPathCall { +func (c *MockCharmReaderNewCharmAtPathCall) Return(arg0 charm0.Charm, arg1 *charm0.URL, arg2 error) *MockCharmReaderNewCharmAtPathCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *MockCharmReaderNewCharmAtPathCall) Do(f func(string) (charm.Charm, *charm.URL, error)) *MockCharmReaderNewCharmAtPathCall { +func (c *MockCharmReaderNewCharmAtPathCall) Do(f func(string) (charm0.Charm, *charm0.URL, error)) *MockCharmReaderNewCharmAtPathCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmReaderNewCharmAtPathCall) DoAndReturn(f func(string) (charm.Charm, *charm.URL, error)) *MockCharmReaderNewCharmAtPathCall { +func (c *MockCharmReaderNewCharmAtPathCall) DoAndReturn(f func(string) (charm0.Charm, *charm0.URL, error)) *MockCharmReaderNewCharmAtPathCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/cmd/juju/application/deployer/mocks/resolver_mock.go b/cmd/juju/application/deployer/mocks/resolver_mock.go index f3c15e98ee1..d42de565820 100644 --- a/cmd/juju/application/deployer/mocks/resolver_mock.go +++ b/cmd/juju/application/deployer/mocks/resolver_mock.go @@ -13,9 +13,9 @@ import ( context "context" reflect "reflect" - charm "github.com/juju/juju/internal/charm" - charm0 "github.com/juju/juju/api/common/charm" + charm "github.com/juju/juju/api/common/charm" base "github.com/juju/juju/core/base" + charm0 "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) @@ -43,10 +43,10 @@ func (m *MockResolver) EXPECT() *MockResolverMockRecorder { } // GetBundle mocks base method. -func (m *MockResolver) GetBundle(arg0 context.Context, arg1 *charm.URL, arg2 charm0.Origin, arg3 string) (charm.Bundle, error) { +func (m *MockResolver) GetBundle(arg0 context.Context, arg1 *charm0.URL, arg2 charm.Origin, arg3 string) (charm0.Bundle, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBundle", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(charm.Bundle) + ret0, _ := ret[0].(charm0.Bundle) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -64,29 +64,29 @@ type MockResolverGetBundleCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockResolverGetBundleCall) Return(arg0 charm.Bundle, arg1 error) *MockResolverGetBundleCall { +func (c *MockResolverGetBundleCall) Return(arg0 charm0.Bundle, arg1 error) *MockResolverGetBundleCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockResolverGetBundleCall) Do(f func(context.Context, *charm.URL, charm0.Origin, string) (charm.Bundle, error)) *MockResolverGetBundleCall { +func (c *MockResolverGetBundleCall) Do(f func(context.Context, *charm0.URL, charm.Origin, string) (charm0.Bundle, error)) *MockResolverGetBundleCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockResolverGetBundleCall) DoAndReturn(f func(context.Context, *charm.URL, charm0.Origin, string) (charm.Bundle, error)) *MockResolverGetBundleCall { +func (c *MockResolverGetBundleCall) DoAndReturn(f func(context.Context, *charm0.URL, charm.Origin, string) (charm0.Bundle, error)) *MockResolverGetBundleCall { c.Call = c.Call.DoAndReturn(f) return c } // ResolveBundleURL mocks base method. -func (m *MockResolver) ResolveBundleURL(arg0 *charm.URL, arg1 charm0.Origin) (*charm.URL, charm0.Origin, error) { +func (m *MockResolver) ResolveBundleURL(arg0 *charm0.URL, arg1 charm.Origin) (*charm0.URL, charm.Origin, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResolveBundleURL", arg0, arg1) - ret0, _ := ret[0].(*charm.URL) - ret1, _ := ret[1].(charm0.Origin) + ret0, _ := ret[0].(*charm0.URL) + ret1, _ := ret[1].(charm.Origin) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -104,29 +104,29 @@ type MockResolverResolveBundleURLCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockResolverResolveBundleURLCall) Return(arg0 *charm.URL, arg1 charm0.Origin, arg2 error) *MockResolverResolveBundleURLCall { +func (c *MockResolverResolveBundleURLCall) Return(arg0 *charm0.URL, arg1 charm.Origin, arg2 error) *MockResolverResolveBundleURLCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *MockResolverResolveBundleURLCall) Do(f func(*charm.URL, charm0.Origin) (*charm.URL, charm0.Origin, error)) *MockResolverResolveBundleURLCall { +func (c *MockResolverResolveBundleURLCall) Do(f func(*charm0.URL, charm.Origin) (*charm0.URL, charm.Origin, error)) *MockResolverResolveBundleURLCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockResolverResolveBundleURLCall) DoAndReturn(f func(*charm.URL, charm0.Origin) (*charm.URL, charm0.Origin, error)) *MockResolverResolveBundleURLCall { +func (c *MockResolverResolveBundleURLCall) DoAndReturn(f func(*charm0.URL, charm.Origin) (*charm0.URL, charm.Origin, error)) *MockResolverResolveBundleURLCall { c.Call = c.Call.DoAndReturn(f) return c } // ResolveCharm mocks base method. -func (m *MockResolver) ResolveCharm(arg0 *charm.URL, arg1 charm0.Origin, arg2 bool) (*charm.URL, charm0.Origin, []base.Base, error) { +func (m *MockResolver) ResolveCharm(arg0 *charm0.URL, arg1 charm.Origin, arg2 bool) (*charm0.URL, charm.Origin, []base.Base, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResolveCharm", arg0, arg1, arg2) - ret0, _ := ret[0].(*charm.URL) - ret1, _ := ret[1].(charm0.Origin) + ret0, _ := ret[0].(*charm0.URL) + ret1, _ := ret[1].(charm.Origin) ret2, _ := ret[2].([]base.Base) ret3, _ := ret[3].(error) return ret0, ret1, ret2, ret3 @@ -145,19 +145,19 @@ type MockResolverResolveCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockResolverResolveCharmCall) Return(arg0 *charm.URL, arg1 charm0.Origin, arg2 []base.Base, arg3 error) *MockResolverResolveCharmCall { +func (c *MockResolverResolveCharmCall) Return(arg0 *charm0.URL, arg1 charm.Origin, arg2 []base.Base, arg3 error) *MockResolverResolveCharmCall { c.Call = c.Call.Return(arg0, arg1, arg2, arg3) return c } // Do rewrite *gomock.Call.Do -func (c *MockResolverResolveCharmCall) Do(f func(*charm.URL, charm0.Origin, bool) (*charm.URL, charm0.Origin, []base.Base, error)) *MockResolverResolveCharmCall { +func (c *MockResolverResolveCharmCall) Do(f func(*charm0.URL, charm.Origin, bool) (*charm0.URL, charm.Origin, []base.Base, error)) *MockResolverResolveCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockResolverResolveCharmCall) DoAndReturn(f func(*charm.URL, charm0.Origin, bool) (*charm.URL, charm0.Origin, []base.Base, error)) *MockResolverResolveCharmCall { +func (c *MockResolverResolveCharmCall) DoAndReturn(f func(*charm0.URL, charm.Origin, bool) (*charm0.URL, charm.Origin, []base.Base, error)) *MockResolverResolveCharmCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/cmd/juju/application/deployer/package_test.go b/cmd/juju/application/deployer/package_test.go index 215d41e052a..3dd28521ea4 100644 --- a/cmd/juju/application/deployer/package_test.go +++ b/cmd/juju/application/deployer/package_test.go @@ -15,7 +15,7 @@ import ( //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/api_mock.go github.com/juju/juju/api AllWatch //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/modelcmd_mock.go github.com/juju/juju/cmd/modelcmd Filesystem //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/write_mock.go io Writer -//go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/charm_mock.go github.com/juju/charm/v13 Charm,Bundle +//go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination mocks/charm_mock.go github.com/juju/juju/internal/charm Charm,Bundle func TestPackage(t *testing.T) { gc.TestingT(t) diff --git a/cmd/juju/application/refresher/charm_mock_test.go b/cmd/juju/application/refresher/charm_mock_test.go index 302ad82befb..18c05582ca0 100644 --- a/cmd/juju/application/refresher/charm_mock_test.go +++ b/cmd/juju/application/refresher/charm_mock_test.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/juju/charm/v13 (interfaces: Charm) +// Source: github.com/juju/juju/internal/charm (interfaces: Charm) // // Generated by this command: // -// mockgen -typed -package refresher -destination charm_mock_test.go github.com/juju/charm/v13 Charm +// mockgen -typed -package refresher -destination charm_mock_test.go github.com/juju/juju/internal/charm Charm // // Package refresher is a generated GoMock package. diff --git a/cmd/juju/application/refresher/package_test.go b/cmd/juju/application/refresher/package_test.go index bbc1a31e419..6de93aea1e3 100644 --- a/cmd/juju/application/refresher/package_test.go +++ b/cmd/juju/application/refresher/package_test.go @@ -11,7 +11,7 @@ import ( //go:generate go run go.uber.org/mock/mockgen -typed -package refresher -destination refresher_mock_test.go github.com/juju/juju/cmd/juju/application/refresher RefresherFactory,Refresher,CharmResolver,CharmRepository //go:generate go run go.uber.org/mock/mockgen -typed -package refresher -destination store_mock_test.go github.com/juju/juju/cmd/juju/application/store CharmAdder -//go:generate go run go.uber.org/mock/mockgen -typed -package refresher -destination charm_mock_test.go github.com/juju/charm/v13 Charm +//go:generate go run go.uber.org/mock/mockgen -typed -package refresher -destination charm_mock_test.go github.com/juju/juju/internal/charm Charm func TestPackage(t *testing.T) { gc.TestingT(t) diff --git a/cmd/juju/application/refresher/refresher_mock_test.go b/cmd/juju/application/refresher/refresher_mock_test.go index a9e39265a8c..04b42b2874b 100644 --- a/cmd/juju/application/refresher/refresher_mock_test.go +++ b/cmd/juju/application/refresher/refresher_mock_test.go @@ -12,9 +12,9 @@ package refresher import ( reflect "reflect" - charm "github.com/juju/juju/internal/charm" - charm0 "github.com/juju/juju/api/common/charm" + charm "github.com/juju/juju/api/common/charm" base "github.com/juju/juju/core/base" + charm0 "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) @@ -243,11 +243,11 @@ func (m *MockCharmResolver) EXPECT() *MockCharmResolverMockRecorder { } // ResolveCharm mocks base method. -func (m *MockCharmResolver) ResolveCharm(arg0 *charm.URL, arg1 charm0.Origin, arg2 bool) (*charm.URL, charm0.Origin, []base.Base, error) { +func (m *MockCharmResolver) ResolveCharm(arg0 *charm0.URL, arg1 charm.Origin, arg2 bool) (*charm0.URL, charm.Origin, []base.Base, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResolveCharm", arg0, arg1, arg2) - ret0, _ := ret[0].(*charm.URL) - ret1, _ := ret[1].(charm0.Origin) + ret0, _ := ret[0].(*charm0.URL) + ret1, _ := ret[1].(charm.Origin) ret2, _ := ret[2].([]base.Base) ret3, _ := ret[3].(error) return ret0, ret1, ret2, ret3 @@ -266,19 +266,19 @@ type MockCharmResolverResolveCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockCharmResolverResolveCharmCall) Return(arg0 *charm.URL, arg1 charm0.Origin, arg2 []base.Base, arg3 error) *MockCharmResolverResolveCharmCall { +func (c *MockCharmResolverResolveCharmCall) Return(arg0 *charm0.URL, arg1 charm.Origin, arg2 []base.Base, arg3 error) *MockCharmResolverResolveCharmCall { c.Call = c.Call.Return(arg0, arg1, arg2, arg3) return c } // Do rewrite *gomock.Call.Do -func (c *MockCharmResolverResolveCharmCall) Do(f func(*charm.URL, charm0.Origin, bool) (*charm.URL, charm0.Origin, []base.Base, error)) *MockCharmResolverResolveCharmCall { +func (c *MockCharmResolverResolveCharmCall) Do(f func(*charm0.URL, charm.Origin, bool) (*charm0.URL, charm.Origin, []base.Base, error)) *MockCharmResolverResolveCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmResolverResolveCharmCall) DoAndReturn(f func(*charm.URL, charm0.Origin, bool) (*charm.URL, charm0.Origin, []base.Base, error)) *MockCharmResolverResolveCharmCall { +func (c *MockCharmResolverResolveCharmCall) DoAndReturn(f func(*charm0.URL, charm.Origin, bool) (*charm0.URL, charm.Origin, []base.Base, error)) *MockCharmResolverResolveCharmCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -307,11 +307,11 @@ func (m *MockCharmRepository) EXPECT() *MockCharmRepositoryMockRecorder { } // NewCharmAtPath mocks base method. -func (m *MockCharmRepository) NewCharmAtPath(arg0 string) (charm.Charm, *charm.URL, error) { +func (m *MockCharmRepository) NewCharmAtPath(arg0 string) (charm0.Charm, *charm0.URL, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewCharmAtPath", arg0) - ret0, _ := ret[0].(charm.Charm) - ret1, _ := ret[1].(*charm.URL) + ret0, _ := ret[0].(charm0.Charm) + ret1, _ := ret[1].(*charm0.URL) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } @@ -329,19 +329,19 @@ type MockCharmRepositoryNewCharmAtPathCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockCharmRepositoryNewCharmAtPathCall) Return(arg0 charm.Charm, arg1 *charm.URL, arg2 error) *MockCharmRepositoryNewCharmAtPathCall { +func (c *MockCharmRepositoryNewCharmAtPathCall) Return(arg0 charm0.Charm, arg1 *charm0.URL, arg2 error) *MockCharmRepositoryNewCharmAtPathCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *MockCharmRepositoryNewCharmAtPathCall) Do(f func(string) (charm.Charm, *charm.URL, error)) *MockCharmRepositoryNewCharmAtPathCall { +func (c *MockCharmRepositoryNewCharmAtPathCall) Do(f func(string) (charm0.Charm, *charm0.URL, error)) *MockCharmRepositoryNewCharmAtPathCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmRepositoryNewCharmAtPathCall) DoAndReturn(f func(string) (charm.Charm, *charm.URL, error)) *MockCharmRepositoryNewCharmAtPathCall { +func (c *MockCharmRepositoryNewCharmAtPathCall) DoAndReturn(f func(string) (charm0.Charm, *charm0.URL, error)) *MockCharmRepositoryNewCharmAtPathCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/cmd/juju/application/refresher/store_mock_test.go b/cmd/juju/application/refresher/store_mock_test.go index 9b9943e19f3..a9878bb671c 100644 --- a/cmd/juju/application/refresher/store_mock_test.go +++ b/cmd/juju/application/refresher/store_mock_test.go @@ -12,8 +12,8 @@ package refresher import ( reflect "reflect" - charm "github.com/juju/juju/internal/charm" - charm0 "github.com/juju/juju/api/common/charm" + charm "github.com/juju/juju/api/common/charm" + charm0 "github.com/juju/juju/internal/charm" gomock "go.uber.org/mock/gomock" ) @@ -41,10 +41,10 @@ func (m *MockCharmAdder) EXPECT() *MockCharmAdderMockRecorder { } // AddCharm mocks base method. -func (m *MockCharmAdder) AddCharm(arg0 *charm.URL, arg1 charm0.Origin, arg2 bool) (charm0.Origin, error) { +func (m *MockCharmAdder) AddCharm(arg0 *charm0.URL, arg1 charm.Origin, arg2 bool) (charm.Origin, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddCharm", arg0, arg1, arg2) - ret0, _ := ret[0].(charm0.Origin) + ret0, _ := ret[0].(charm.Origin) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -62,28 +62,28 @@ type MockCharmAdderAddCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockCharmAdderAddCharmCall) Return(arg0 charm0.Origin, arg1 error) *MockCharmAdderAddCharmCall { +func (c *MockCharmAdderAddCharmCall) Return(arg0 charm.Origin, arg1 error) *MockCharmAdderAddCharmCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockCharmAdderAddCharmCall) Do(f func(*charm.URL, charm0.Origin, bool) (charm0.Origin, error)) *MockCharmAdderAddCharmCall { +func (c *MockCharmAdderAddCharmCall) Do(f func(*charm0.URL, charm.Origin, bool) (charm.Origin, error)) *MockCharmAdderAddCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmAdderAddCharmCall) DoAndReturn(f func(*charm.URL, charm0.Origin, bool) (charm0.Origin, error)) *MockCharmAdderAddCharmCall { +func (c *MockCharmAdderAddCharmCall) DoAndReturn(f func(*charm0.URL, charm.Origin, bool) (charm.Origin, error)) *MockCharmAdderAddCharmCall { c.Call = c.Call.DoAndReturn(f) return c } // AddLocalCharm mocks base method. -func (m *MockCharmAdder) AddLocalCharm(arg0 *charm.URL, arg1 charm.Charm, arg2 bool) (*charm.URL, error) { +func (m *MockCharmAdder) AddLocalCharm(arg0 *charm0.URL, arg1 charm0.Charm, arg2 bool) (*charm0.URL, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddLocalCharm", arg0, arg1, arg2) - ret0, _ := ret[0].(*charm.URL) + ret0, _ := ret[0].(*charm0.URL) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -101,25 +101,25 @@ type MockCharmAdderAddLocalCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockCharmAdderAddLocalCharmCall) Return(arg0 *charm.URL, arg1 error) *MockCharmAdderAddLocalCharmCall { +func (c *MockCharmAdderAddLocalCharmCall) Return(arg0 *charm0.URL, arg1 error) *MockCharmAdderAddLocalCharmCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockCharmAdderAddLocalCharmCall) Do(f func(*charm.URL, charm.Charm, bool) (*charm.URL, error)) *MockCharmAdderAddLocalCharmCall { +func (c *MockCharmAdderAddLocalCharmCall) Do(f func(*charm0.URL, charm0.Charm, bool) (*charm0.URL, error)) *MockCharmAdderAddLocalCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmAdderAddLocalCharmCall) DoAndReturn(f func(*charm.URL, charm.Charm, bool) (*charm.URL, error)) *MockCharmAdderAddLocalCharmCall { +func (c *MockCharmAdderAddLocalCharmCall) DoAndReturn(f func(*charm0.URL, charm0.Charm, bool) (*charm0.URL, error)) *MockCharmAdderAddLocalCharmCall { c.Call = c.Call.DoAndReturn(f) return c } // CheckCharmPlacement mocks base method. -func (m *MockCharmAdder) CheckCharmPlacement(arg0 string, arg1 *charm.URL) error { +func (m *MockCharmAdder) CheckCharmPlacement(arg0 string, arg1 *charm0.URL) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CheckCharmPlacement", arg0, arg1) ret0, _ := ret[0].(error) @@ -145,13 +145,13 @@ func (c *MockCharmAdderCheckCharmPlacementCall) Return(arg0 error) *MockCharmAdd } // Do rewrite *gomock.Call.Do -func (c *MockCharmAdderCheckCharmPlacementCall) Do(f func(string, *charm.URL) error) *MockCharmAdderCheckCharmPlacementCall { +func (c *MockCharmAdderCheckCharmPlacementCall) Do(f func(string, *charm0.URL) error) *MockCharmAdderCheckCharmPlacementCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmAdderCheckCharmPlacementCall) DoAndReturn(f func(string, *charm.URL) error) *MockCharmAdderCheckCharmPlacementCall { +func (c *MockCharmAdderCheckCharmPlacementCall) DoAndReturn(f func(string, *charm0.URL) error) *MockCharmAdderCheckCharmPlacementCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/cmd/juju/application/store/mocks/charm_mock.go b/cmd/juju/application/store/mocks/charm_mock.go index b70beeecc5e..430514d416f 100644 --- a/cmd/juju/application/store/mocks/charm_mock.go +++ b/cmd/juju/application/store/mocks/charm_mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/juju/charm/v13 (interfaces: Bundle) +// Source: github.com/juju/juju/internal/charm (interfaces: Bundle) // // Generated by this command: // -// mockgen -typed -package mocks -destination ./mocks/charm_mock.go github.com/juju/charm/v13 Bundle +// mockgen -typed -package mocks -destination ./mocks/charm_mock.go github.com/juju/juju/internal/charm Bundle // // Package mocks is a generated GoMock package. diff --git a/cmd/juju/application/store/mocks/store_mock.go b/cmd/juju/application/store/mocks/store_mock.go index 74f05ef0a07..36f1c25b7b7 100644 --- a/cmd/juju/application/store/mocks/store_mock.go +++ b/cmd/juju/application/store/mocks/store_mock.go @@ -14,9 +14,9 @@ import ( url "net/url" reflect "reflect" - charm "github.com/juju/juju/internal/charm" charms "github.com/juju/juju/api/client/charms" - charm0 "github.com/juju/juju/api/common/charm" + charm "github.com/juju/juju/api/common/charm" + charm0 "github.com/juju/juju/internal/charm" charmhub "github.com/juju/juju/internal/charmhub" gomock "go.uber.org/mock/gomock" ) @@ -45,10 +45,10 @@ func (m *MockCharmAdder) EXPECT() *MockCharmAdderMockRecorder { } // AddCharm mocks base method. -func (m *MockCharmAdder) AddCharm(arg0 *charm.URL, arg1 charm0.Origin, arg2 bool) (charm0.Origin, error) { +func (m *MockCharmAdder) AddCharm(arg0 *charm0.URL, arg1 charm.Origin, arg2 bool) (charm.Origin, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddCharm", arg0, arg1, arg2) - ret0, _ := ret[0].(charm0.Origin) + ret0, _ := ret[0].(charm.Origin) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -66,28 +66,28 @@ type MockCharmAdderAddCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockCharmAdderAddCharmCall) Return(arg0 charm0.Origin, arg1 error) *MockCharmAdderAddCharmCall { +func (c *MockCharmAdderAddCharmCall) Return(arg0 charm.Origin, arg1 error) *MockCharmAdderAddCharmCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockCharmAdderAddCharmCall) Do(f func(*charm.URL, charm0.Origin, bool) (charm0.Origin, error)) *MockCharmAdderAddCharmCall { +func (c *MockCharmAdderAddCharmCall) Do(f func(*charm0.URL, charm.Origin, bool) (charm.Origin, error)) *MockCharmAdderAddCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmAdderAddCharmCall) DoAndReturn(f func(*charm.URL, charm0.Origin, bool) (charm0.Origin, error)) *MockCharmAdderAddCharmCall { +func (c *MockCharmAdderAddCharmCall) DoAndReturn(f func(*charm0.URL, charm.Origin, bool) (charm.Origin, error)) *MockCharmAdderAddCharmCall { c.Call = c.Call.DoAndReturn(f) return c } // AddLocalCharm mocks base method. -func (m *MockCharmAdder) AddLocalCharm(arg0 *charm.URL, arg1 charm.Charm, arg2 bool) (*charm.URL, error) { +func (m *MockCharmAdder) AddLocalCharm(arg0 *charm0.URL, arg1 charm0.Charm, arg2 bool) (*charm0.URL, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddLocalCharm", arg0, arg1, arg2) - ret0, _ := ret[0].(*charm.URL) + ret0, _ := ret[0].(*charm0.URL) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -105,25 +105,25 @@ type MockCharmAdderAddLocalCharmCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockCharmAdderAddLocalCharmCall) Return(arg0 *charm.URL, arg1 error) *MockCharmAdderAddLocalCharmCall { +func (c *MockCharmAdderAddLocalCharmCall) Return(arg0 *charm0.URL, arg1 error) *MockCharmAdderAddLocalCharmCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockCharmAdderAddLocalCharmCall) Do(f func(*charm.URL, charm.Charm, bool) (*charm.URL, error)) *MockCharmAdderAddLocalCharmCall { +func (c *MockCharmAdderAddLocalCharmCall) Do(f func(*charm0.URL, charm0.Charm, bool) (*charm0.URL, error)) *MockCharmAdderAddLocalCharmCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmAdderAddLocalCharmCall) DoAndReturn(f func(*charm.URL, charm.Charm, bool) (*charm.URL, error)) *MockCharmAdderAddLocalCharmCall { +func (c *MockCharmAdderAddLocalCharmCall) DoAndReturn(f func(*charm0.URL, charm0.Charm, bool) (*charm0.URL, error)) *MockCharmAdderAddLocalCharmCall { c.Call = c.Call.DoAndReturn(f) return c } // CheckCharmPlacement mocks base method. -func (m *MockCharmAdder) CheckCharmPlacement(arg0 string, arg1 *charm.URL) error { +func (m *MockCharmAdder) CheckCharmPlacement(arg0 string, arg1 *charm0.URL) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CheckCharmPlacement", arg0, arg1) ret0, _ := ret[0].(error) @@ -149,13 +149,13 @@ func (c *MockCharmAdderCheckCharmPlacementCall) Return(arg0 error) *MockCharmAdd } // Do rewrite *gomock.Call.Do -func (c *MockCharmAdderCheckCharmPlacementCall) Do(f func(string, *charm.URL) error) *MockCharmAdderCheckCharmPlacementCall { +func (c *MockCharmAdderCheckCharmPlacementCall) Do(f func(string, *charm0.URL) error) *MockCharmAdderCheckCharmPlacementCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmAdderCheckCharmPlacementCall) DoAndReturn(f func(string, *charm.URL) error) *MockCharmAdderCheckCharmPlacementCall { +func (c *MockCharmAdderCheckCharmPlacementCall) DoAndReturn(f func(string, *charm0.URL) error) *MockCharmAdderCheckCharmPlacementCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -184,7 +184,7 @@ func (m *MockCharmsAPI) EXPECT() *MockCharmsAPIMockRecorder { } // GetDownloadInfo mocks base method. -func (m *MockCharmsAPI) GetDownloadInfo(arg0 *charm.URL, arg1 charm0.Origin) (charms.DownloadInfo, error) { +func (m *MockCharmsAPI) GetDownloadInfo(arg0 *charm0.URL, arg1 charm.Origin) (charms.DownloadInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDownloadInfo", arg0, arg1) ret0, _ := ret[0].(charms.DownloadInfo) @@ -211,13 +211,13 @@ func (c *MockCharmsAPIGetDownloadInfoCall) Return(arg0 charms.DownloadInfo, arg1 } // Do rewrite *gomock.Call.Do -func (c *MockCharmsAPIGetDownloadInfoCall) Do(f func(*charm.URL, charm0.Origin) (charms.DownloadInfo, error)) *MockCharmsAPIGetDownloadInfoCall { +func (c *MockCharmsAPIGetDownloadInfoCall) Do(f func(*charm0.URL, charm.Origin) (charms.DownloadInfo, error)) *MockCharmsAPIGetDownloadInfoCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockCharmsAPIGetDownloadInfoCall) DoAndReturn(f func(*charm.URL, charm0.Origin) (charms.DownloadInfo, error)) *MockCharmsAPIGetDownloadInfoCall { +func (c *MockCharmsAPIGetDownloadInfoCall) DoAndReturn(f func(*charm0.URL, charm.Origin) (charms.DownloadInfo, error)) *MockCharmsAPIGetDownloadInfoCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -285,14 +285,14 @@ func (m *MockDownloadBundleClient) EXPECT() *MockDownloadBundleClientMockRecorde } // DownloadAndReadBundle mocks base method. -func (m *MockDownloadBundleClient) DownloadAndReadBundle(arg0 context.Context, arg1 *url.URL, arg2 string, arg3 ...charmhub.DownloadOption) (charm.Bundle, error) { +func (m *MockDownloadBundleClient) DownloadAndReadBundle(arg0 context.Context, arg1 *url.URL, arg2 string, arg3 ...charmhub.DownloadOption) (charm0.Bundle, error) { m.ctrl.T.Helper() varargs := []any{arg0, arg1, arg2} for _, a := range arg3 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "DownloadAndReadBundle", varargs...) - ret0, _ := ret[0].(charm.Bundle) + ret0, _ := ret[0].(charm0.Bundle) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -311,19 +311,19 @@ type MockDownloadBundleClientDownloadAndReadBundleCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockDownloadBundleClientDownloadAndReadBundleCall) Return(arg0 charm.Bundle, arg1 error) *MockDownloadBundleClientDownloadAndReadBundleCall { +func (c *MockDownloadBundleClientDownloadAndReadBundleCall) Return(arg0 charm0.Bundle, arg1 error) *MockDownloadBundleClientDownloadAndReadBundleCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockDownloadBundleClientDownloadAndReadBundleCall) Do(f func(context.Context, *url.URL, string, ...charmhub.DownloadOption) (charm.Bundle, error)) *MockDownloadBundleClientDownloadAndReadBundleCall { +func (c *MockDownloadBundleClientDownloadAndReadBundleCall) Do(f func(context.Context, *url.URL, string, ...charmhub.DownloadOption) (charm0.Bundle, error)) *MockDownloadBundleClientDownloadAndReadBundleCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockDownloadBundleClientDownloadAndReadBundleCall) DoAndReturn(f func(context.Context, *url.URL, string, ...charmhub.DownloadOption) (charm.Bundle, error)) *MockDownloadBundleClientDownloadAndReadBundleCall { +func (c *MockDownloadBundleClientDownloadAndReadBundleCall) DoAndReturn(f func(context.Context, *url.URL, string, ...charmhub.DownloadOption) (charm0.Bundle, error)) *MockDownloadBundleClientDownloadAndReadBundleCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/cmd/juju/application/store/package_test.go b/cmd/juju/application/store/package_test.go index bafbd508771..bbc70a0b12f 100644 --- a/cmd/juju/application/store/package_test.go +++ b/cmd/juju/application/store/package_test.go @@ -10,7 +10,7 @@ import ( ) //go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination ./mocks/store_mock.go github.com/juju/juju/cmd/juju/application/store CharmAdder,CharmsAPI,DownloadBundleClient -//go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination ./mocks/charm_mock.go github.com/juju/charm/v13 Bundle +//go:generate go run go.uber.org/mock/mockgen -typed -package mocks -destination ./mocks/charm_mock.go github.com/juju/juju/internal/charm Bundle func TestPackage(t *testing.T) { gc.TestingT(t) diff --git a/core/charm/charm_mock_test.go b/core/charm/charm_mock_test.go index 68678d65787..54062ecc050 100644 --- a/core/charm/charm_mock_test.go +++ b/core/charm/charm_mock_test.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/juju/charm/v13 (interfaces: CharmMeta) +// Source: github.com/juju/juju/internal/charm (interfaces: CharmMeta) // // Generated by this command: // -// mockgen -typed -package charm -destination charm_mock_test.go github.com/juju/charm/v13 CharmMeta +// mockgen -typed -package charm -destination charm_mock_test.go github.com/juju/juju/internal/charm CharmMeta // // Package charm is a generated GoMock package. diff --git a/core/charm/package_test.go b/core/charm/package_test.go index c52c51a0a2c..4a9f31e80b8 100644 --- a/core/charm/package_test.go +++ b/core/charm/package_test.go @@ -9,7 +9,7 @@ import ( gc "gopkg.in/check.v1" ) -//go:generate go run go.uber.org/mock/mockgen -typed -package charm -destination charm_mock_test.go github.com/juju/charm/v13 CharmMeta +//go:generate go run go.uber.org/mock/mockgen -typed -package charm -destination charm_mock_test.go github.com/juju/juju/internal/charm CharmMeta //go:generate go run go.uber.org/mock/mockgen -typed -package charm -destination core_charm_mock_test.go github.com/juju/juju/core/charm SelectorModelConfig func TestPackage(t *testing.T) { diff --git a/core/model/charm_mock_test.go b/core/model/charm_mock_test.go index b40b3c09eb1..9be28413b41 100644 --- a/core/model/charm_mock_test.go +++ b/core/model/charm_mock_test.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/juju/charm/v13 (interfaces: CharmMeta) +// Source: github.com/juju/juju/internal/charm (interfaces: CharmMeta) // // Generated by this command: // -// mockgen -typed -package model_test -destination charm_mock_test.go github.com/juju/charm/v13 CharmMeta +// mockgen -typed -package model_test -destination charm_mock_test.go github.com/juju/juju/internal/charm CharmMeta // // Package model_test is a generated GoMock package. diff --git a/core/model/package_test.go b/core/model/package_test.go index db0f02623bf..3dad89f736b 100644 --- a/core/model/package_test.go +++ b/core/model/package_test.go @@ -9,7 +9,7 @@ import ( gc "gopkg.in/check.v1" ) -//go:generate go run go.uber.org/mock/mockgen -typed -package model_test -destination charm_mock_test.go github.com/juju/charm/v13 CharmMeta +//go:generate go run go.uber.org/mock/mockgen -typed -package model_test -destination charm_mock_test.go github.com/juju/juju/internal/charm CharmMeta func TestPackage(t *testing.T) { gc.TestingT(t) diff --git a/scripts/charmhub/locate-series-less-charms.go b/scripts/charmhub/locate-series-less-charms.go index 2fe7063471d..bb5bc68bcb6 100644 --- a/scripts/charmhub/locate-series-less-charms.go +++ b/scripts/charmhub/locate-series-less-charms.go @@ -29,8 +29,7 @@ func main() { } type metadata struct { - Series []string `yaml:"series"` - Containers map[string]interface{} `yaml:"containers"` + Deployment map[any]any `json:"Deployment"` } for _, result := range results { @@ -38,6 +37,8 @@ func main() { continue } + fmt.Println("Checking", result.Name) + info, err := client.Info(context.TODO(), result.Name) if err != nil { log.Fatal(err) @@ -48,8 +49,8 @@ func main() { log.Fatal(err) } - if len(meta.Series) == 0 && len(meta.Containers) == 0 { - fmt.Println(result.Name) + if len(meta.Deployment) != 0 { + fmt.Println("FOUND!", result.Name, meta.Deployment) } } } From 3545649461249ed924865cfb3035685f52f52e1f Mon Sep 17 00:00:00 2001 From: Jack Shaw Date: Tue, 21 May 2024 17:59:49 +0100 Subject: [PATCH 9/9] Add "internal/charm" imports to allow list of core Many tests began failing when we pulled "juju/charm" into "internal", now that it exists in juju/juju Fix tests --- api/package_test.go | 4 ++++ cmd/containeragent/initialize/package_test.go | 4 ++++ core/base/package_test.go | 4 ++++ core/migration/package_test.go | 1 + core/settings/package_test.go | 9 ++++++++- core/watcher/eventsource/package_test.go | 1 + core/watcher/package_test.go | 1 + 7 files changed, 23 insertions(+), 1 deletion(-) diff --git a/api/package_test.go b/api/package_test.go index a6558f9188a..2609a48bd82 100644 --- a/api/package_test.go +++ b/api/package_test.go @@ -55,6 +55,10 @@ func (*ImportSuite) TestImports(c *gc.C) { "domain/secret/errors", "domain/secretbackend/errors", "environs/envcontext", + "internal/charm", + "internal/charm/assumes", + "internal/charm/hooks", + "internal/charm/resource", "internal/feature", "internal/logger", "internal/proxy", diff --git a/cmd/containeragent/initialize/package_test.go b/cmd/containeragent/initialize/package_test.go index 19f6e03205a..b0d28d73949 100644 --- a/cmd/containeragent/initialize/package_test.go +++ b/cmd/containeragent/initialize/package_test.go @@ -82,6 +82,10 @@ func (*importSuite) TestImports(c *gc.C) { "environs/config", "environs/envcontext", "environs/tags", + "internal/charm", + "internal/charm/assumes", + "internal/charm/hooks", + "internal/charm/resource", "internal/charmhub", "internal/charmhub/path", "internal/charmhub/transport", diff --git a/core/base/package_test.go b/core/base/package_test.go index 031a8c13ae9..23e1b44abc4 100644 --- a/core/base/package_test.go +++ b/core/base/package_test.go @@ -27,6 +27,10 @@ func (*ImportTest) TestImports(c *gc.C) { c.Assert(found, jc.SameContents, []string{ "core/logger", "core/os/ostype", + "internal/charm", + "internal/charm/assumes", + "internal/charm/hooks", + "internal/charm/resource", "internal/logger", }) } diff --git a/core/migration/package_test.go b/core/migration/package_test.go index b9e497b8550..6d7697e0f31 100644 --- a/core/migration/package_test.go +++ b/core/migration/package_test.go @@ -30,6 +30,7 @@ func (*ImportTest) TestImports(c *gc.C) { "core/logger", "core/network", "core/resources", + "internal/charm/resource", "internal/logger", }) } diff --git a/core/settings/package_test.go b/core/settings/package_test.go index fdf3edf7285..89e2c7064bb 100644 --- a/core/settings/package_test.go +++ b/core/settings/package_test.go @@ -24,5 +24,12 @@ func (*importSuite) TestImports(c *gc.C) { found := coretesting.FindJujuCoreImports(c, "github.com/juju/juju/core/settings") // This package only brings in other core packages. - c.Assert(found, jc.SameContents, []string{}) + c.Assert(found, jc.SameContents, []string{ + "core/logger", + "internal/charm", + "internal/charm/assumes", + "internal/charm/hooks", + "internal/charm/resource", + "internal/logger", + }) } diff --git a/core/watcher/eventsource/package_test.go b/core/watcher/eventsource/package_test.go index 093bdd801d8..f4bc7716319 100644 --- a/core/watcher/eventsource/package_test.go +++ b/core/watcher/eventsource/package_test.go @@ -44,6 +44,7 @@ func (*ImportTest) TestImports(c *gc.C) { "core/secrets", "core/status", "core/watcher", + "internal/charm/resource", "internal/logger", }) diff --git a/core/watcher/package_test.go b/core/watcher/package_test.go index 38ae6c9e858..f9c71769f96 100644 --- a/core/watcher/package_test.go +++ b/core/watcher/package_test.go @@ -32,6 +32,7 @@ func (s *ImportTest) TestImports(c *gc.C) { "core/resources", "core/secrets", "core/status", + "internal/charm/resource", "internal/logger", })