From c9aaaadfb02fe6e0e919f5e16374c542dbbb183e Mon Sep 17 00:00:00 2001 From: Jack Shaw Date: Tue, 7 May 2024 13:25:46 +0100 Subject: [PATCH 01/11] Deprecate series in bundles Show a deprecation warning when deploying a bundle with series --- cmd/juju/application/bundle/bundle.go | 32 ++++++++++++++++++++-- cmd/juju/application/bundle/bundle_test.go | 21 ++++++++++---- cmd/juju/application/deployer/bundle.go | 2 +- cmd/juju/application/diffbundle.go | 2 +- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/cmd/juju/application/bundle/bundle.go b/cmd/juju/application/bundle/bundle.go index 0286fde7e14..a3a314d25ac 100644 --- a/cmd/juju/application/bundle/bundle.go +++ b/cmd/juju/application/bundle/bundle.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/juju/charm/v11" + "github.com/juju/cmd/v3" "github.com/juju/errors" "github.com/juju/names/v4" @@ -250,7 +251,7 @@ func applicationConfigValue(key string, valueMap interface{}) (interface{}, erro // combined bundle data. Returns a slice of errors encountered while // 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) { +func ComposeAndVerifyBundle(ctx *cmd.Context, base BundleDataSource, pathToOverlays []string) (*charm.BundleData, []error, error) { var dsList []charm.BundleDataSource unMarshallErrors := make([]error, 0) unMarshallErrors = append(unMarshallErrors, gatherErrors(base)...) @@ -269,7 +270,7 @@ func ComposeAndVerifyBundle(base BundleDataSource, pathToOverlays []string) (*ch if err != nil { return nil, nil, errors.Trace(err) } - if err = verifyBundle(bundleData, base.BasePath()); err != nil { + if err = verifyBundle(ctx, bundleData, base.BasePath()); err != nil { return nil, nil, errors.Trace(err) } @@ -287,7 +288,7 @@ func gatherErrors(ds BundleDataSource) []error { return returnErrors } -func verifyBundle(data *charm.BundleData, bundleDir string) error { +func verifyBundle(ctx *cmd.Context, data *charm.BundleData, bundleDir string) error { verifyConstraints := func(s string) error { _, err := constraints.Parse(s) return err @@ -301,6 +302,8 @@ func verifyBundle(data *charm.BundleData, bundleDir string) error { return err } + deprecationWarningForSeries(ctx, data) + var errs []string // This method cannot be included within data.Verify because // to verify corresponding series and base match we need to be @@ -326,6 +329,29 @@ func verifyBundle(data *charm.BundleData, bundleDir string) error { return errors.Trace(verifyError) } +func deprecationWarningForSeries(ctx *cmd.Context, data *charm.BundleData) { + includeSeries := false + if data.Series != "" { + includeSeries = true + } + for _, m := range data.Machines { + if m != nil && m.Series != "" { + includeSeries = true + break + } + } + for _, app := range data.Applications { + if app != nil && app.Series != "" { + includeSeries = true + break + } + } + + if includeSeries { + ctx.Warningf("series in being deprecated in favour of bases. For more information about the transition to bases see https://discourse.charmhub.io/t/transition-from-series-to-base-in-juju-4-0/14127") + } +} + func verifyMixedSeriesBasesMatch(data *charm.BundleData) error { if data == nil { return nil diff --git a/cmd/juju/application/bundle/bundle_test.go b/cmd/juju/application/bundle/bundle_test.go index 5726fa2a270..7bbe1a59fe8 100644 --- a/cmd/juju/application/bundle/bundle_test.go +++ b/cmd/juju/application/bundle/bundle_test.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/juju/charm/v11" + "github.com/juju/cmd/v3" "github.com/juju/errors" jc "github.com/juju/testing/checkers" "go.uber.org/mock/gomock" @@ -215,8 +216,10 @@ func (s *composeAndVerifyRepSuite) TestComposeAndVerifyBundleNoOverlay(c *gc.C) c.Assert(err, jc.ErrorIsNil) s.expectParts(&charm.BundleDataPart{Data: bundleData}) s.expectBasePath() + ctx, err := cmd.DefaultContext() + c.Assert(err, jc.ErrorIsNil) - obtained, _, err := ComposeAndVerifyBundle(s.bundleDataSource, nil) + obtained, _, err := ComposeAndVerifyBundle(ctx, s.bundleDataSource, nil) c.Assert(err, jc.ErrorIsNil) c.Assert(obtained, gc.DeepEquals, bundleData) } @@ -228,13 +231,15 @@ func (s *composeAndVerifyRepSuite) TestComposeAndVerifyBundleOverlay(c *gc.C) { s.expectParts(&charm.BundleDataPart{Data: bundleData}) s.expectBasePath() s.setupOverlayFile(c) + ctx, err := cmd.DefaultContext() + c.Assert(err, jc.ErrorIsNil) expected := *bundleData expected.Applications["wordpress"].Options = map[string]interface{}{ "blog-title": "magic bundle config", } - obtained, _, err := ComposeAndVerifyBundle(s.bundleDataSource, []string{s.overlayFile}) + obtained, _, err := ComposeAndVerifyBundle(ctx, s.bundleDataSource, []string{s.overlayFile}) c.Assert(err, jc.ErrorIsNil) c.Assert(obtained, gc.DeepEquals, &expected) } @@ -250,13 +255,15 @@ func (s *composeAndVerifyRepSuite) TestComposeAndVerifyBundleOverlayUnmarshallEr }) s.expectBasePath() s.setupOverlayFile(c) + ctx, err := cmd.DefaultContext() + c.Assert(err, jc.ErrorIsNil) expected := *bundleData expected.Applications["wordpress"].Options = map[string]interface{}{ "blog-title": "magic bundle config", } - obtained, unmarshallErrors, err := ComposeAndVerifyBundle(s.bundleDataSource, []string{s.overlayFile}) + obtained, unmarshallErrors, err := ComposeAndVerifyBundle(ctx, s.bundleDataSource, []string{s.overlayFile}) c.Assert(err, jc.ErrorIsNil) c.Assert(obtained, gc.DeepEquals, &expected) c.Assert(unmarshallErrors, gc.HasLen, 1) @@ -269,8 +276,10 @@ func (s *composeAndVerifyRepSuite) TestComposeAndVerifyBundleMixingBaseAndSeries c.Assert(err, jc.ErrorIsNil) s.expectParts(&charm.BundleDataPart{Data: bundleData}) s.expectBasePath() + ctx, err := cmd.DefaultContext() + c.Assert(err, jc.ErrorIsNil) - obtained, _, err := ComposeAndVerifyBundle(s.bundleDataSource, nil) + obtained, _, err := ComposeAndVerifyBundle(ctx, s.bundleDataSource, nil) c.Assert(err, jc.ErrorIsNil) c.Assert(obtained, gc.DeepEquals, bundleData) } @@ -281,8 +290,10 @@ func (s *composeAndVerifyRepSuite) TestComposeAndVerifyBundleMixingBaseAndSeries c.Assert(err, jc.ErrorIsNil) s.expectParts(&charm.BundleDataPart{Data: bundleData}) s.expectBasePath() + ctx, err := cmd.DefaultContext() + c.Assert(err, jc.ErrorIsNil) - obtained, _, err := ComposeAndVerifyBundle(s.bundleDataSource, nil) + obtained, _, err := ComposeAndVerifyBundle(ctx, s.bundleDataSource, nil) c.Assert(err, gc.ErrorMatches, `(?s)the provided bundle has the following errors:.*application "wordpress" series "jammy" and base "ubuntu@20.04" must match if both supplied.*invalid constraints.*`) c.Assert(obtained, gc.IsNil) } diff --git a/cmd/juju/application/deployer/bundle.go b/cmd/juju/application/deployer/bundle.go index 0a11d383467..e0fa8a47919 100644 --- a/cmd/juju/application/deployer/bundle.go +++ b/cmd/juju/application/deployer/bundle.go @@ -93,7 +93,7 @@ func (d *deployBundle) deploy( // Compose bundle to be deployed and check its validity before running // any pre/post checks. - bundleData, unmarshalErrors, err := bundle.ComposeAndVerifyBundle(d.bundleDataSource, d.bundleOverlayFile) + bundleData, unmarshalErrors, err := bundle.ComposeAndVerifyBundle(ctx, d.bundleDataSource, d.bundleOverlayFile) if err != nil { return errors.Annotatef(err, "cannot deploy bundle") } diff --git a/cmd/juju/application/diffbundle.go b/cmd/juju/application/diffbundle.go index 8a325cd764c..5249bc336f9 100644 --- a/cmd/juju/application/diffbundle.go +++ b/cmd/juju/application/diffbundle.go @@ -229,7 +229,7 @@ func (c *diffBundleCommand) Run(ctx *cmd.Context) error { return errors.Trace(err) } - bundle, _, err := appbundle.ComposeAndVerifyBundle(baseSrc, c.bundleOverlays) + bundle, _, err := appbundle.ComposeAndVerifyBundle(ctx, baseSrc, c.bundleOverlays) if err != nil { return errors.Trace(err) } From 487fe17c5084bfb976ce53225c2e2e45ba15c925 Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Fri, 24 May 2024 16:57:59 +0100 Subject: [PATCH 02/11] External controller log message Whilst debugging a production issue, we saw the log line that controller info was updated, but we don't know what it went from and to. As this happened a lot, it would be helpful to better understand why we're constantly getting a new set of addresses. Neither the cert or the alias are updated when we get new information, so it doesn't make sense to keep spitting them out. Just the addresses are enough to get an idea of the churn. --- .../externalcontrollerupdater.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/worker/externalcontrollerupdater/externalcontrollerupdater.go b/worker/externalcontrollerupdater/externalcontrollerupdater.go index a98dda183fa..817ad5efcb1 100644 --- a/worker/externalcontrollerupdater/externalcontrollerupdater.go +++ b/worker/externalcontrollerupdater/externalcontrollerupdater.go @@ -247,20 +247,26 @@ func (w *controllerWatcher) loop() error { if reflect.DeepEqual(newInfo.Addrs, info.Addrs) { continue } + // API addresses have changed. Save the details to the // local controller and stop the existing notify watcher // and set it to nil, so we'll restart it with the new // addresses. - info.Addrs = newInfo.Addrs if err := w.setExternalControllerInfo(crossmodel.ControllerInfo{ ControllerTag: w.tag, Alias: info.Alias, - Addrs: info.Addrs, + Addrs: newInfo.Addrs, CACert: info.CACert, }); err != nil { return errors.Annotate(err, "caching external controller info") } - logger.Infof("new controller info for controller %q: %v", w.tag.Id(), info) + + logger.Infof("new controller info for controller %q: addresses changed: new %v, prev %v", w.tag.Id(), newInfo.Addrs, info.Addrs) + + // Set the new addresses in the info struct so that + // we can reuse it in the next iteration. + info.Addrs = newInfo.Addrs + if err := worker.Stop(nw); err != nil { return errors.Trace(err) } From d64c75452088885cae5316af8d16d044490391ea Mon Sep 17 00:00:00 2001 From: jujubot Date: Mon, 27 May 2024 02:11:08 +0000 Subject: [PATCH 03/11] Increment juju to 3.5.2 --- scripts/win-installer/setup.iss | 2 +- snap/snapcraft.yaml | 2 +- version/version.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/win-installer/setup.iss b/scripts/win-installer/setup.iss index 681ed47565c..d1f8190fcf2 100644 --- a/scripts/win-installer/setup.iss +++ b/scripts/win-installer/setup.iss @@ -4,7 +4,7 @@ #if GetEnv('JUJU_VERSION') != "" #define MyAppVersion=GetEnv('JUJU_VERSION') #else -#define MyAppVersion="3.5.1" +#define MyAppVersion="3.5.2" #endif #define MyAppName "Juju" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 932b8ea7e1e..5e845214076 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: juju -version: 3.5.1 +version: 3.5.2 summary: Juju - a model-driven operator lifecycle manager for K8s and machines license: AGPL-3.0 description: | diff --git a/version/version.go b/version/version.go index 05cd3a61093..0f9f76af473 100644 --- a/version/version.go +++ b/version/version.go @@ -18,7 +18,7 @@ import ( // The presence and format of this constant is very important. // The debian/rules build recipe uses this value for the version // number of the release package. -const version = "3.5.1" +const version = "3.5.2" // UserAgentVersion defines a user agent version used for communication for // outside resources. From 39da04ab107245930d700ed77a6818ae4686ed4e Mon Sep 17 00:00:00 2001 From: Jack Shaw Date: Tue, 23 Apr 2024 15:09:07 +0100 Subject: [PATCH 04/11] Add constant for Ubuntu OS in bases In many places in the codebase, understandably, we treat the string "ubuntu" as a magic string for the OS field in bases Centralise this magic string as a const in core/base --- .../facades/client/machinemanager/upgradebase.go | 4 ++-- core/base/base.go | 16 +++++++++++++--- environs/bootstrap/bootstrap.go | 4 ++-- provider/common/bootstrap.go | 4 ++-- provider/dummy/environs.go | 2 +- provider/equinix/environ.go | 8 +++++--- provider/oci/images.go | 4 +--- state/charm.go | 4 ++-- upgrades/upgradevalidation/validation.go | 2 +- version/series.go | 2 +- 10 files changed, 30 insertions(+), 20 deletions(-) diff --git a/apiserver/facades/client/machinemanager/upgradebase.go b/apiserver/facades/client/machinemanager/upgradebase.go index b672ab56b58..88e0792339c 100644 --- a/apiserver/facades/client/machinemanager/upgradebase.go +++ b/apiserver/facades/client/machinemanager/upgradebase.go @@ -310,7 +310,7 @@ func makeUpgradeSeriesValidator(client CharmhubClient) upgradeSeriesValidator { } func baseFromParams(machineTag string, base state.Base, channel string) (corebase.Base, error) { - if base.OS != "ubuntu" { + if base.OS != corebase.UbuntuOS { return corebase.Base{}, errors.Errorf("%s is running %s and is not valid for Ubuntu series upgrade", machineTag, base.OS) } @@ -327,7 +327,7 @@ func (s upgradeSeriesValidator) ValidateBase(requestedBase, machineBase corebase return errors.BadRequestf("base missing from args") } - if requestedBase.OS != "ubuntu" { + if requestedBase.OS != corebase.UbuntuOS { return errors.Errorf("base %q is not a valid upgrade target", requestedBase) } diff --git a/core/base/base.go b/core/base/base.go index 1a7130d8a76..967e601dc8c 100644 --- a/core/base/base.go +++ b/core/base/base.go @@ -21,6 +21,16 @@ type Base struct { Channel Channel } +const ( + // UbuntuOS is the special value to be places in OS field of a base to + // indicate an operating system is an Ubuntu distro + UbuntuOS = "ubuntu" + + // CentosOS is the special value to be places in OS field of a base to + // indicate an operating system is a CentOS distro + CentosOS = "centos" +) + // ParseBase constructs a Base from the os and channel string. func ParseBase(os string, channel string) (Base, error) { if os == "" && channel == "" { @@ -155,9 +165,9 @@ func GetSeriesFromChannel(name string, channel string) (string, error) { func GetSeriesFromBase(v Base) (string, error) { var osSeries map[SeriesName]seriesVersion switch strings.ToLower(v.OS) { - case "ubuntu": + case UbuntuOS: osSeries = ubuntuSeries - case "centos": + case CentosOS: osSeries = centosSeries } for s, vers := range osSeries { @@ -170,7 +180,7 @@ func GetSeriesFromBase(v Base) (string, error) { // LegacyKubernetesBase is the ubuntu base image for legacy k8s charms. func LegacyKubernetesBase() Base { - return MakeDefaultBase("ubuntu", "20.04") + return MakeDefaultBase(UbuntuOS, "20.04") } // LegacyKubernetesSeries is the ubuntu series for legacy k8s charms. diff --git a/environs/bootstrap/bootstrap.go b/environs/bootstrap/bootstrap.go index 458b342a39f..0d33ba5ec68 100644 --- a/environs/bootstrap/bootstrap.go +++ b/environs/bootstrap/bootstrap.go @@ -390,8 +390,8 @@ func bootstrapIAAS( if !args.Force && err != nil { // If the series isn't valid at all, then don't prompt users to use // the --force flag. - if _, err := corebase.UbuntuBaseVersion(requestedBootstrapBase); err != nil { - return errors.NotValidf("base %q", requestedBootstrapBase.String()) + if requestedBootstrapBase.OS != corebase.UbuntuOS { + return errors.NotValidf("non-ubuntu bootstrap base %q", requestedBootstrapBase.String()) } return errors.Annotatef(err, "use --force to override") } diff --git a/provider/common/bootstrap.go b/provider/common/bootstrap.go index acdded875da..a6531d9672c 100644 --- a/provider/common/bootstrap.go +++ b/provider/common/bootstrap.go @@ -114,8 +114,8 @@ func BootstrapInstance( if !args.Force && err != nil { // If the base isn't valid at all, then don't prompt users to use // the --force flag. - if _, err := corebase.UbuntuBaseVersion(requestedBootstrapBase); err != nil { - return nil, nil, nil, errors.NotValidf("base %q", requestedBootstrapBase.String()) + if requestedBootstrapBase.OS != corebase.UbuntuOS { + return nil, nil, nil, errors.NotValidf("non-ubuntu bootstrap base %q", requestedBootstrapBase.String()) } return nil, nil, nil, errors.Annotatef(err, "use --force to override") } diff --git a/provider/dummy/environs.go b/provider/dummy/environs.go index 4a750192f3d..802ade08881 100644 --- a/provider/dummy/environs.go +++ b/provider/dummy/environs.go @@ -1055,7 +1055,7 @@ func (e *environ) Bootstrap(ctx environs.BootstrapContext, callCtx context.Provi bsResult := &environs.BootstrapResult{ Arch: arch, - Base: corebase.MakeDefaultBase("ubuntu", "22.04"), + Base: corebase.MakeDefaultBase(corebase.UbuntuOS, "22.04"), CloudBootstrapFinalizer: finalize, } return bsResult, nil diff --git a/provider/equinix/environ.go b/provider/equinix/environ.go index 9049fefa692..f313e6002a6 100644 --- a/provider/equinix/environ.go +++ b/provider/equinix/environ.go @@ -45,7 +45,9 @@ import ( var logger = loggo.GetLogger("juju.provider.equinix") -const sshPort = 22 +const ( + sshPort = 22 +) type environConfig struct { config *config.Config @@ -328,7 +330,7 @@ func getCloudConfig(args environs.StartInstanceParams) (cloudinit.CloudConfig, e // NOTE(achilleasa): this is a hack and is only meant to be used // temporarily; we must ensure that equinix mirrors the official // ubuntu cloud images. - if _, err := corebase.GetSeriesFromBase(args.InstanceConfig.Base); err == nil { + if args.InstanceConfig.Base.OS == corebase.UbuntuOS { cloudCfg.AddScripts( "apt-get update", "DEBIAN_FRONTEND=noninteractive apt-get --option=Dpkg::Options::=--force-confdef --option=Dpkg::Options::=--force-confold --option=Dpkg::Options::=--force-unsafe-io --assume-yes --quiet install dmidecode snapd", @@ -340,7 +342,7 @@ func getCloudConfig(args environs.StartInstanceParams) (cloudinit.CloudConfig, e // references the juju-assigned hostname before localhost. Otherwise, // running 'hostname -f' would return localhost whereas 'hostname' // returns the juju-assigned host (see LP1956538). - if _, err := corebase.GetSeriesFromBase(args.InstanceConfig.Base); err == nil { + if args.InstanceConfig.Base.OS == corebase.UbuntuOS { cloudCfg.AddScripts( `sed -i -e "/127\.0\.0\.1/c\127\.0\.0\.1 $(hostname) localhost" /etc/hosts`, ) diff --git a/provider/oci/images.go b/provider/oci/images.go index fee978cc306..9ccd1398e1e 100644 --- a/provider/oci/images.go +++ b/provider/oci/images.go @@ -39,8 +39,6 @@ const ( centOS = "CentOS" ubuntuOS = "Canonical Ubuntu" - UbuntuBase = "ubuntu" - staleImageCacheTimeoutInMinutes = 30 ) @@ -322,7 +320,7 @@ func parseUbuntuImage(img ociCore.Image) (corebase.Base, string, bool) { // the ubuntu image's metadata) so we need to find a workaround as // explained in the NOTE a few lines below. channel, postfix, _ := strings.Cut(*img.OperatingSystemVersion, " ") - base = corebase.MakeDefaultBase(UbuntuBase, channel) + base = corebase.MakeDefaultBase(corebase.UbuntuOS, channel) // if not found, means that the OperatingSystemVersion only contained // the channel. if strings.Contains(*img.DisplayName, "Minimal") || diff --git a/state/charm.go b/state/charm.go index 60105429d65..42836132d8e 100644 --- a/state/charm.go +++ b/state/charm.go @@ -81,12 +81,12 @@ func (b Base) String() string { // UbuntuBase is used in tests. func UbuntuBase(channel string) Base { - return Base{OS: "ubuntu", Channel: channel + "/stable"} + return Base{OS: corebase.UbuntuOS, Channel: channel + "/stable"} } // DefaultLTSBase is used in tests. func DefaultLTSBase() Base { - return Base{OS: "ubuntu", Channel: jujuversion.DefaultSupportedLTSBase().Channel.String()} + return Base{OS: corebase.UbuntuOS, Channel: jujuversion.DefaultSupportedLTSBase().Channel.String()} } // Platform identifies the platform the charm was installed on. diff --git a/upgrades/upgradevalidation/validation.go b/upgrades/upgradevalidation/validation.go index 94474b9438e..2d6535865d1 100644 --- a/upgrades/upgradevalidation/validation.go +++ b/upgrades/upgradevalidation/validation.go @@ -196,7 +196,7 @@ func checkForDeprecatedUbuntuSeriesForModel( supported := false var deprecatedBases []state.Base for _, vers := range corebase.UbuntuVersions(&supported, nil) { - deprecatedBases = append(deprecatedBases, state.Base{OS: "ubuntu", Channel: vers}) + deprecatedBases = append(deprecatedBases, state.Base{OS: corebase.UbuntuOS, Channel: vers}) } // sort for tests. diff --git a/version/series.go b/version/series.go index d891ba62fd6..afbb0169aa0 100644 --- a/version/series.go +++ b/version/series.go @@ -14,5 +14,5 @@ func DefaultSupportedLTS() string { // DefaultSupportedLTSBase returns the latest LTS base that Juju supports // and is compatible with. func DefaultSupportedLTSBase() corebase.Base { - return corebase.MakeDefaultBase("ubuntu", "22.04") + return corebase.MakeDefaultBase(corebase.UbuntuOS, "22.04") } From 01cbd3c99007ba8797f37b0b11092d830f64c24b Mon Sep 17 00:00:00 2001 From: Jack Shaw Date: Fri, 24 May 2024 13:50:38 +0100 Subject: [PATCH 05/11] Add IsUbuntuLTS method to bases Sometimes we will want to be able to tell if an ubuntu version is an LTS or not For instance, Ubuntu images on Azure have differently formatted ids depending on whether they're an LTS or not --- core/base/base.go | 19 +++++++++++++++++++ core/base/base_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/core/base/base.go b/core/base/base.go index 967e601dc8c..eb496e944d0 100644 --- a/core/base/base.go +++ b/core/base/base.go @@ -124,6 +124,25 @@ func (b Base) IsCompatible(other Base) bool { return b.OS == other.OS && b.Channel.Track == other.Channel.Track } +// ubuntuLTSes lists the Ubuntu LTS releases that +// this version of Juju knows about +var ubuntuLTSes = []Base{ + MakeDefaultBase(UbuntuOS, "20.04"), + MakeDefaultBase(UbuntuOS, "22.04"), + MakeDefaultBase(UbuntuOS, "24.04"), +} + +// IsUbuntuLTS returns true if this base is a recognised +// Ubuntu LTS. +func (b Base) IsUbuntuLTS() bool { + for _, ubuntuLTS := range ubuntuLTSes { + if b.IsCompatible(ubuntuLTS) { + return true + } + } + return false +} + // DisplayString returns the base string ignoring risk. func (b Base) DisplayString() string { if b.Channel.Track == "" || b.OS == "" { diff --git a/core/base/base_test.go b/core/base/base_test.go index 72df1071df8..1a1f78cf3eb 100644 --- a/core/base/base_test.go +++ b/core/base/base_test.go @@ -100,3 +100,41 @@ func (s *BaseSuite) TestParseManifestBases(c *gc.C) { } c.Assert(obtained, jc.DeepEquals, expected) } + +var ubuntuLTS = []Base{ + MustParseBaseFromString("ubuntu@20.04"), + MustParseBaseFromString("ubuntu@22.04"), + MustParseBaseFromString("ubuntu@24.04"), + MustParseBaseFromString("ubuntu@24.04/stable"), + MustParseBaseFromString("ubuntu@24.04/edge"), +} + +func (s *BaseSuite) TestIsUbuntuLTSForLTSes(c *gc.C) { + for i, lts := range ubuntuLTS { + c.Logf("Checking index %d base %v", i, lts) + c.Check(lts.IsUbuntuLTS(), jc.IsTrue) + } +} + +var nonUbuntuLTS = []Base{ + MustParseBaseFromString("ubuntu@17.04"), + MustParseBaseFromString("ubuntu@19.04"), + MustParseBaseFromString("ubuntu@21.04"), + + MustParseBaseFromString("ubuntu@18.10"), + MustParseBaseFromString("ubuntu@20.10"), + MustParseBaseFromString("ubuntu@22.10"), + + MustParseBaseFromString("ubuntu@22.04-blah"), + MustParseBaseFromString("ubuntu@22.04.1234"), + + MustParseBaseFromString("centos@7"), + MustParseBaseFromString("centos@20.04"), +} + +func (s *BaseSuite) TestIsUbuntuLTSForNonLTSes(c *gc.C) { + for i, lts := range nonUbuntuLTS { + c.Logf("Checking index %d base %v", i, lts) + c.Check(lts.IsUbuntuLTS(), jc.IsFalse) + } +} From ee075f4f738d10fd2eb8f890f80dff4ac6915783 Mon Sep 17 00:00:00 2001 From: Alastair Flynn Date: Wed, 29 May 2024 13:59:17 +0100 Subject: [PATCH 06/11] Remove link to discorse post in docs --- cmd/juju/cloud/addcredential.go | 4 ---- cmd/juju/cloud/addcredential_test.go | 3 --- provider/gce/credentials.go | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/cmd/juju/cloud/addcredential.go b/cmd/juju/cloud/addcredential.go index 4b2be368897..9dc5f1b368d 100644 --- a/cmd/juju/cloud/addcredential.go +++ b/cmd/juju/cloud/addcredential.go @@ -91,10 +91,6 @@ Use --controller option to upload a credential to a controller. Use --client option to add a credential to the current client. -Further help: -Please visit https://discourse.charmhub.io/t/1508 for cloud-specific -instructions. - ` const usageAddCredentialExamples = ` diff --git a/cmd/juju/cloud/addcredential_test.go b/cmd/juju/cloud/addcredential_test.go index a42a3ff041c..aebef437042 100644 --- a/cmd/juju/cloud/addcredential_test.go +++ b/cmd/juju/cloud/addcredential_test.go @@ -917,7 +917,6 @@ Enter credential name: Using auth-type "jsonfile". Enter path to the .json file containing a service account key for your project -(detailed instructions available at https://discourse.charmhub.io/t/1508). Path: `[1:] stderr := ` @@ -973,7 +972,6 @@ Enter your choice, or type Q|q to quit: Enter credential name: Using auth-type "jsonfile". Enter path to the .json file containing a service account key for your project -(detailed instructions available at https://discourse.charmhub.io/t/1508). Path: Credential "blah" added locally for cloud "remote". @@ -1002,7 +1000,6 @@ Enter credential name: Using auth-type "jsonfile". Enter path to the .json file containing a service account key for your project -(detailed instructions available at https://discourse.charmhub.io/t/1508). Path: `[1:] stderr := ` diff --git a/provider/gce/credentials.go b/provider/gce/credentials.go index 4ea7b031f7d..a59cd77ce5e 100644 --- a/provider/gce/credentials.go +++ b/provider/gce/credentials.go @@ -51,7 +51,7 @@ func (environProviderCredentials) CredentialSchemas() map[cloud.AuthType]cloud.C cloud.JSONFileAuthType: {{ Name: credAttrFile, CredentialAttr: cloud.CredentialAttr{ - Description: "path to the .json file containing a service account key for your project\n(detailed instructions available at https://discourse.charmhub.io/t/1508).\nPath", + Description: "path to the .json file containing a service account key for your project\nPath", FilePath: true, }, }}, From f05df559fb71e98d9b8b630d481098214247ade0 Mon Sep 17 00:00:00 2001 From: Ian Booth Date: Thu, 30 May 2024 13:29:16 +1000 Subject: [PATCH 07/11] Quote regexp metadata when using secret labels in regexp queries for dupe checks --- state/secrets.go | 2 ++ state/secrets_test.go | 46 ++++++++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/state/secrets.go b/state/secrets.go index d872b2dd5e0..3aa806fb76f 100644 --- a/state/secrets.go +++ b/state/secrets.go @@ -5,6 +5,7 @@ package state import ( "fmt" + "regexp" "strconv" "strings" "time" @@ -1333,6 +1334,7 @@ func (st *State) uniqueSecretLabelBaseOps(tag names.Tag, label string) (ops []tx errorMsg string ) + label = regexp.QuoteMeta(label) switch tag := tag.(type) { case names.ApplicationTag: // Ensure no units use this label for both owner and consumer label. diff --git a/state/secrets_test.go b/state/secrets_test.go index 24f43b480c6..031e1bbe2d2 100644 --- a/state/secrets_test.go +++ b/state/secrets_test.go @@ -176,7 +176,7 @@ func (s *SecretsSuite) TestCreateBackendRef(c *gc.C) { }) } -func (s *SecretsSuite) TestCreateDuplicateLabelApplicationOwned(c *gc.C) { +func (s *SecretsSuite) assertCreateDuplicateLabelApplicationOwned(c *gc.C, label string) { uri := secrets.NewURI() now := s.Clock.Now().Round(time.Second).UTC() next := now.Add(time.Minute).Round(time.Second).UTC() @@ -189,7 +189,7 @@ func (s *SecretsSuite) TestCreateDuplicateLabelApplicationOwned(c *gc.C) { RotatePolicy: ptr(secrets.RotateDaily), NextRotateTime: ptr(next), Description: ptr("my secret"), - Label: ptr("foobar"), + Label: ptr(label), ExpireTime: ptr(expire), Params: nil, Data: map[string]string{"foo": "bar"}, @@ -209,14 +209,22 @@ func (s *SecretsSuite) TestCreateDuplicateLabelApplicationOwned(c *gc.C) { // Existing application owner label should not be used for consumer label for its units. cmd := &secrets.SecretConsumerMetadata{ - Label: "foobar", + Label: label, CurrentRevision: md.LatestRevision, } err = s.State.SaveSecretConsumer(uri, s.ownerUnit.Tag(), cmd) c.Assert(errors.Is(err, state.LabelExists), jc.IsTrue) } -func (s *SecretsSuite) TestCreateDuplicateLabelUnitOwned(c *gc.C) { +func (s *SecretsSuite) TestCreateDuplicateLabelApplicationOwned(c *gc.C) { + s.assertCreateDuplicateLabelApplicationOwned(c, "foobar") +} + +func (s *SecretsSuite) TestCreateDuplicateLabelApplicationOwnedSpecialChars(c *gc.C) { + s.assertCreateDuplicateLabelApplicationOwned(c, `\U.++foo`) +} + +func (s *SecretsSuite) assertCreateDuplicateLabelUnitOwned(c *gc.C, label string) { uri := secrets.NewURI() now := s.Clock.Now().Round(time.Second).UTC() next := now.Add(time.Minute).Round(time.Second).UTC() @@ -229,7 +237,7 @@ func (s *SecretsSuite) TestCreateDuplicateLabelUnitOwned(c *gc.C) { RotatePolicy: ptr(secrets.RotateDaily), NextRotateTime: ptr(next), Description: ptr("my secret"), - Label: ptr("foobar"), + Label: ptr(label), ExpireTime: ptr(expire), Params: nil, Data: map[string]string{"foo": "bar"}, @@ -249,14 +257,22 @@ func (s *SecretsSuite) TestCreateDuplicateLabelUnitOwned(c *gc.C) { // Existing unit owner label should not be used for consumer label for the application. cmd := &secrets.SecretConsumerMetadata{ - Label: "foobar", + Label: label, CurrentRevision: md.LatestRevision, } err = s.State.SaveSecretConsumer(uri, s.owner.Tag(), cmd) c.Assert(errors.Is(err, state.LabelExists), jc.IsTrue) } -func (s *SecretsSuite) TestCreateDuplicateLabelUnitConsumed(c *gc.C) { +func (s *SecretsSuite) TestCreateDuplicateLabelUnitOwned(c *gc.C) { + s.assertCreateDuplicateLabelUnitOwned(c, "foobar") +} + +func (s *SecretsSuite) TestCreateDuplicateLabelUnitOwnedSpecialChars(c *gc.C) { + s.assertCreateDuplicateLabelUnitOwned(c, `\U.++foo`) +} + +func (s *SecretsSuite) assertCreateDuplicateLabelUnitConsumed(c *gc.C, label string) { uri := secrets.NewURI() p := state.CreateSecretParams{ Version: 1, @@ -271,7 +287,7 @@ func (s *SecretsSuite) TestCreateDuplicateLabelUnitConsumed(c *gc.C) { c.Assert(err, jc.ErrorIsNil) cmd := &secrets.SecretConsumerMetadata{ - Label: "foobar", + Label: label, CurrentRevision: md.LatestRevision, } err = s.State.SaveSecretConsumer(uri, s.ownerUnit.Tag(), cmd) @@ -280,26 +296,34 @@ func (s *SecretsSuite) TestCreateDuplicateLabelUnitConsumed(c *gc.C) { // Existing unit consumer label should not be used for owner label. uri2 := secrets.NewURI() p.Owner = s.ownerUnit.Tag() - p.Label = ptr("foobar") + p.Label = ptr(label) _, err = s.store.CreateSecret(uri2, p) c.Assert(errors.Is(err, state.LabelExists), jc.IsTrue) // Existing unit consumer label should not be used for owner label for the application. uri3 := secrets.NewURI() p.Owner = s.owner.Tag() - p.Label = ptr("foobar") + p.Label = ptr(label) _, err = s.store.CreateSecret(uri3, p) c.Assert(errors.Is(err, state.LabelExists), jc.IsTrue) // Existing unit consumer label should not be used for consumer label for the application. cmd = &secrets.SecretConsumerMetadata{ - Label: "foobar", + Label: label, CurrentRevision: md.LatestRevision, } err = s.State.SaveSecretConsumer(uri, s.owner.Tag(), cmd) c.Assert(errors.Is(err, state.LabelExists), jc.IsTrue) } +func (s *SecretsSuite) TestCreateDuplicateLabelUnitConsumed(c *gc.C) { + s.assertCreateDuplicateLabelUnitConsumed(c, "foobar") +} + +func (s *SecretsSuite) TestCreateDuplicateLabelUnitConsumedSpecialChars(c *gc.C) { + s.assertCreateDuplicateLabelUnitConsumed(c, `\U.++foo`) +} + func (s *SecretsSuite) TestCreateDyingOwner(c *gc.C) { err := s.owner.Destroy() c.Assert(err, jc.ErrorIsNil) From 4c8af969baffd315e06f2f0ad76fb6418c551234 Mon Sep 17 00:00:00 2001 From: Harry Pidcock Date: Thu, 30 May 2024 17:47:51 +1000 Subject: [PATCH 08/11] Fix uses of BOOTSTRAP_PROVIDER. Fix lxd kvm usage. --- tests/includes/juju.sh | 6 +++--- tests/main.sh | 9 +-------- tests/suites/backup/backup.sh | 2 +- tests/suites/cli/model_defaults.sh | 2 +- tests/suites/constraints/constraints.sh | 2 +- tests/suites/deploy/deploy_bundles.sh | 4 ++-- tests/suites/deploy/deploy_charms.sh | 6 +++--- tests/suites/deploy/os.sh | 2 +- tests/suites/manual/deploy_manual.sh | 4 ++-- tests/suites/manual/spaces.sh | 2 +- 10 files changed, 16 insertions(+), 23 deletions(-) diff --git a/tests/includes/juju.sh b/tests/includes/juju.sh index 96497097b27..2fe60e03177 100644 --- a/tests/includes/juju.sh +++ b/tests/includes/juju.sh @@ -69,10 +69,10 @@ bootstrap() { "azure") cloud="azure" ;; - "localhost" | "lxd") - cloud="localhost" + "lxd") + cloud="${BOOTSTRAP_CLOUD:-localhost}" ;; - "lxd-remote" | "vsphere" | "openstack" | "k8s" | "maas") + "vsphere" | "openstack" | "k8s" | "maas") cloud="${BOOTSTRAP_CLOUD}" ;; "manual") diff --git a/tests/main.sh b/tests/main.sh index 4310356adf8..e7c49d0d718 100755 --- a/tests/main.sh +++ b/tests/main.sh @@ -6,7 +6,7 @@ export SHELLCHECK_OPTS="-e SC2230 -e SC2039 -e SC2028 -e SC2002 -e SC2005 -e SC2 export BOOTSTRAP_REUSE_LOCAL="${BOOTSTRAP_REUSE_LOCAL:-}" export BOOTSTRAP_REUSE="${BOOTSTRAP_REUSE:-false}" export BOOTSTRAP_PROVIDER="${BOOTSTRAP_PROVIDER:-lxd}" -export BOOTSTRAP_CLOUD="${BOOTSTRAP_CLOUD:-lxd}" +export BOOTSTRAP_CLOUD="${BOOTSTRAP_CLOUD:-localhost}" export BOOTSTRAP_SERIES="${BOOTSTRAP_SERIES:-}" export BOOTSTRAP_ARCH="${BOOTSTRAP_ARCH:-}" export BUILD_ARCH="${BUILD_ARCH:-}" @@ -193,13 +193,6 @@ while getopts "hH?vAs:a:x:rl:p:c:R:S:" opt; do CLOUD=$(juju show-controller "${OPTARG}" --format=json 2>/dev/null | jq -r ".[\"${OPTARG}\"] | .details | .cloud") PROVIDER=$(juju clouds --client --all --format=json 2>/dev/null | jq -r ".[\"${CLOUD}\"] | .type") - if [[ -z ${PROVIDER} ]]; then - PROVIDER="${CLOUD}" - fi - # We want "ec2" to redirect to "aws". This is needed e.g. for the ck tests - if [[ ${PROVIDER} == "ec2" ]]; then - PROVIDER="aws" - fi export BOOTSTRAP_PROVIDER="${PROVIDER}" export BOOTSTRAP_CLOUD="${CLOUD}" ;; diff --git a/tests/suites/backup/backup.sh b/tests/suites/backup/backup.sh index 9570aac9402..dca81cfd544 100644 --- a/tests/suites/backup/backup.sh +++ b/tests/suites/backup/backup.sh @@ -59,7 +59,7 @@ run_basic_backup_restore() { juju status --format json | jq '.machines | length' | check 1 # Only do this check if provider is LXD (too hard to do for all providers) - if [ "${BOOTSTRAP_PROVIDER}" == "lxd" ] || [ "${BOOTSTRAP_PROVIDER}" == "localhost" ]; then + if [ "${BOOTSTRAP_PROVIDER}" == "lxd" ]; then echo "Ensure that both instances are running (restore shouldn't terminate machines)" lxc list --format json | jq --arg name "${id0}" -r '.[] | select(.name==$name) | .state.status' | check Running lxc list --format json | jq --arg name "${id1}" -r '.[] | select(.name==$name) | .state.status' | check Running diff --git a/tests/suites/cli/model_defaults.sh b/tests/suites/cli/model_defaults.sh index fd1fb51dc42..40920f5ebc5 100644 --- a/tests/suites/cli/model_defaults.sh +++ b/tests/suites/cli/model_defaults.sh @@ -62,7 +62,7 @@ test_model_defaults() { run "run_model_defaults_boolean" case "${BOOTSTRAP_PROVIDER-}" in - "aws") + "ec2") run "run_model_defaults_region_aws" ;; *) diff --git a/tests/suites/constraints/constraints.sh b/tests/suites/constraints/constraints.sh index 820c89e0a5f..0ceca4621b6 100644 --- a/tests/suites/constraints/constraints.sh +++ b/tests/suites/constraints/constraints.sh @@ -10,7 +10,7 @@ test_constraints_common() { cd .. || exit case "${BOOTSTRAP_PROVIDER:-}" in - "lxd" | "lxd-remote" | "localhost") + "lxd") run "run_constraints_lxd" ;; "openstack") diff --git a/tests/suites/deploy/deploy_bundles.sh b/tests/suites/deploy/deploy_bundles.sh index 84a4b9dbeb0..38d19d48bed 100644 --- a/tests/suites/deploy/deploy_bundles.sh +++ b/tests/suites/deploy/deploy_bundles.sh @@ -380,7 +380,7 @@ test_deploy_bundles() { # LXD specific profile tests. case "${BOOTSTRAP_PROVIDER:-}" in - "lxd" | "localhost") + "lxd") run "run_deploy_lxd_profile_bundle_openstack" echo "==> TEST SKIPPED: deploy_lxd_profile_bundle - tests for non LXD only" ;; @@ -392,7 +392,7 @@ test_deploy_bundles() { # AWS specific image id tests. case "${BOOTSTRAP_PROVIDER:-}" in - "ec2" | "aws") + "ec2") check_dependencies aws add_clean_func "run_cleanup_ami" export ami_id diff --git a/tests/suites/deploy/deploy_charms.sh b/tests/suites/deploy/deploy_charms.sh index 8b190c55a2a..9ca9af3dfcf 100644 --- a/tests/suites/deploy/deploy_charms.sh +++ b/tests/suites/deploy/deploy_charms.sh @@ -24,8 +24,8 @@ run_deploy_charm_placement_directive() { expected_base="ubuntu@20.04" # Setup machines for placement based on provider used for test. # Container in container doesn't work consistently enough, - # for test. Use kvm via lxc instead. - if [[ ${BOOTSTRAP_PROVIDER} == "lxd" ]] || [[ ${BOOTSTRAP_PROVIDER} == "localhost" ]]; then + # for test. Use kvm via lxc instead if it is available. + if [[ ${BOOTSTRAP_PROVIDER} == "lxd" ]] && stat /dev/kvm; then juju add-machine --base "${expected_base}" --constraints="virt-type=virtual-machine" else juju add-machine --base "${expected_base}" @@ -358,7 +358,7 @@ test_deploy_charms() { run "run_deploy_charm_unsupported_series" case "${BOOTSTRAP_PROVIDER:-}" in - "lxd" | "localhost") + "lxd") run "run_deploy_lxd_to_machine" run "run_deploy_lxd_profile_charm" run "run_deploy_local_predeployed_charm" diff --git a/tests/suites/deploy/os.sh b/tests/suites/deploy/os.sh index d7ce13f6b08..320532bd321 100644 --- a/tests/suites/deploy/os.sh +++ b/tests/suites/deploy/os.sh @@ -10,7 +10,7 @@ test_deploy_os() { cd .. || exit case "${BOOTSTRAP_PROVIDER:-}" in - "ec2" | "aws") + "ec2") # # A handy place to find the current AMIs for centos # https://wiki.centos.org/Cloud/AWS diff --git a/tests/suites/manual/deploy_manual.sh b/tests/suites/manual/deploy_manual.sh index fe4ba714937..931ca2ac75b 100644 --- a/tests/suites/manual/deploy_manual.sh +++ b/tests/suites/manual/deploy_manual.sh @@ -10,10 +10,10 @@ test_deploy_manual() { cd .. || exit case "${BOOTSTRAP_PROVIDER:-}" in - "lxd" | "lxd-remote" | "localhost") + "lxd") run "run_deploy_manual_lxd" ;; - "aws" | "ec2") + "aws") run "run_deploy_manual_aws" ;; *) diff --git a/tests/suites/manual/spaces.sh b/tests/suites/manual/spaces.sh index 8f104e61f9d..59c3c304eb0 100644 --- a/tests/suites/manual/spaces.sh +++ b/tests/suites/manual/spaces.sh @@ -10,7 +10,7 @@ test_spaces_manual() { cd .. || exit case "${BOOTSTRAP_PROVIDER:-}" in - "aws") + "ec2") run "run_spaces_manual_aws" ;; *) From 50a1e23a15c9781060b465fd0368bcf7588dc44c Mon Sep 17 00:00:00 2001 From: Harry Pidcock Date: Thu, 30 May 2024 20:02:50 +1000 Subject: [PATCH 09/11] Fix bad python escapes. --- acceptancetests/jujupy/client.py | 10 +++++----- acceptancetests/jujupy/tests/test_status.py | 2 +- acceptancetests/repository/trusty/haproxy/cm.py | 4 ++-- acceptancetests/tests/test_jujucharm.py | 2 +- scripts/find-bad-doc-comments.py | 4 ++-- scripts/leadershipclaimer/count-leadership.py | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/acceptancetests/jujupy/client.py b/acceptancetests/jujupy/client.py index 752b13b9e61..3f460c85ffc 100644 --- a/acceptancetests/jujupy/client.py +++ b/acceptancetests/jujupy/client.py @@ -1881,7 +1881,7 @@ def backup(self): raise log.info('backup file {}'.format(output)) backup_file_pattern = re.compile( - '(juju-backup-[0-9-]+\.(t|tar.)gz)'.encode('ascii')) + '(juju-backup-[0-9-]+\\.(t|tar.)gz)'.encode('ascii')) match = backup_file_pattern.search(output) if match is None: raise Exception("The backup file was not found in output: %s" % @@ -2280,7 +2280,7 @@ def handle_openstack(self, child, cloud): child.expect(self.REGION_ENDPOINT_PROMPT) child.sendline(values['endpoint']) match = child.expect([ - u"Enter another region\? \([yY]/[nN]\):", + u"Enter another region\\? \\([yY]/[nN]\\):", u"Can't validate endpoint" ]) if match == 1: @@ -2292,7 +2292,7 @@ def handle_openstack(self, child, cloud): def handle_vsphere(self, child, cloud): match = child.expect([u"Enter a name for your .* cloud:", - u'Enter the (vCenter address or URL|API endpoint url for the cloud \[\]):']) + u'Enter the (vCenter address or URL|API endpoint url for the cloud \\[\\]):']) if match == 0: raise NameNotAccepted('Cloud name not accepted.') if match == 1: @@ -2308,7 +2308,7 @@ def handle_vsphere(self, child, cloud): raise InvalidEndpoint() child.sendline(name) child.expect( - u'Enter another (datacenter|region)\? \([yY]/[nN]\):') + u'Enter another (datacenter|region)\\? \\([yY]/[nN]\\):') if num + 1 < len(cloud['regions']): child.sendline('y') else: @@ -2417,7 +2417,7 @@ def register_user_interactively(client, token, controller_name): child.sendline(password) child.expect(u'Confirm password:') child.sendline(password) - child.expect(u'Enter a name for this controller( \[.*\])?:') + child.expect(u'Enter a name for this controller( \\[.*\\])?:') child.sendline(controller_name) def login_if_need(session): diff --git a/acceptancetests/jujupy/tests/test_status.py b/acceptancetests/jujupy/tests/test_status.py index 0c0efae7ec3..9c8bca35082 100644 --- a/acceptancetests/jujupy/tests/test_status.py +++ b/acceptancetests/jujupy/tests/test_status.py @@ -80,7 +80,7 @@ def test_to_exception_stuck_allocating(self): current='allocating', message='foo') with self.assertRaisesRegexp( StuckAllocatingError, - "\('0', 'Stuck allocating. Last message: foo'\)"): + "\\('0', 'Stuck allocating. Last message: foo'\\)"): raise item.to_exception() def test_to_exception_allocating_unit(self): diff --git a/acceptancetests/repository/trusty/haproxy/cm.py b/acceptancetests/repository/trusty/haproxy/cm.py index f728b8608f4..72775f991f5 100644 --- a/acceptancetests/repository/trusty/haproxy/cm.py +++ b/acceptancetests/repository/trusty/haproxy/cm.py @@ -32,7 +32,7 @@ def get_branch_config(config_file): line = line.split('#')[0].strip() bzr_match = re.match(r'(\S+)\s+' 'lp:([^;]+)' - '(?:;revno=(\d+))?', line) + '(?:;revno=(\\d+))?', line) if bzr_match: name, branch, revno = bzr_match.group(1, 2, 3) if revno is None: @@ -42,7 +42,7 @@ def get_branch_config(config_file): branches[name] = (branch, revspec) continue dir_match = re.match(r'(\S+)\s+' - '\(directory\)', line) + '\\(directory\\)', line) if dir_match: name = dir_match.group(1) branches[name] = None diff --git a/acceptancetests/tests/test_jujucharm.py b/acceptancetests/tests/test_jujucharm.py index f7e9c79732f..ffc7a5e7231 100644 --- a/acceptancetests/tests/test_jujucharm.py +++ b/acceptancetests/tests/test_jujucharm.py @@ -118,7 +118,7 @@ def test_ensure_valid_name(self): self.assertIsNone(Charm.NAME_REGEX.match(charm.metadata['name'])) self.assertRaisesRegexp( JujuAssertionError, - 'Invalid Juju Charm Name, "BAD_NAME" does not match ".*"\.', + 'Invalid Juju Charm Name, "BAD_NAME" does not match ".*"\\.', Charm, 'BAD_NAME', 'A charm with a checked bad name') def test_ensure_valid_name_anchoring(self): diff --git a/scripts/find-bad-doc-comments.py b/scripts/find-bad-doc-comments.py index 42bdee56536..196ca628459 100755 --- a/scripts/find-bad-doc-comments.py +++ b/scripts/find-bad-doc-comments.py @@ -31,8 +31,8 @@ def find_go_files(root): yield path.join(directory, filename) DOC_COMMENT_PATT = '\n\n//.+\n(//.+\n)*func.+\n' -FIRST_WORD_PATT = '// *(\w+)' -FUNC_NAME_PATT = 'func(?: \([^)]+\))? (\S+)\(' +FIRST_WORD_PATT = '// *(\\w+)' +FUNC_NAME_PATT = 'func(?: \\([^)]+\\))? (\\S+)\\(' def extract_doc_comments(text): for match in re.finditer(DOC_COMMENT_PATT, text, re.MULTILINE): diff --git a/scripts/leadershipclaimer/count-leadership.py b/scripts/leadershipclaimer/count-leadership.py index ddc66e425f0..c872213815b 100644 --- a/scripts/leadershipclaimer/count-leadership.py +++ b/scripts/leadershipclaimer/count-leadership.py @@ -13,9 +13,9 @@ def main(args): p.add_argument("--tick", type=float, default=1.0, help="seconds between printing status ticks") opts = p.parse_args(args) - actionsRE = re.compile("\s*(?P