Example #1
0
def status(snap_name, arch, experimental_progressive_releases):
    """Get the status on the store for <snap-name>.

    \b
    Examples:
        snapcraft status my-snap
    """
    if experimental_progressive_releases:
        os.environ["SNAPCRAFT_EXPERIMENTAL_PROGRESSIVE_RELEASES"] = "Y"
        echo.warning("*EXPERIMENTAL* progressive releases in use.")

    snap_channel_map = StoreClientCLI().get_snap_channel_map(
        snap_name=snap_name)
    existing_architectures = snap_channel_map.get_existing_architectures()

    if not snap_channel_map.channel_map:
        echo.warning("This snap has no released revisions.")
    elif arch and arch not in existing_architectures:
        echo.warning(f"No revisions for architecture {arch!r}.")
    else:
        if arch:
            architectures = (arch, )
        else:
            architectures = existing_architectures
        click.echo(
            get_tabulated_channel_map(snap_channel_map,
                                      architectures=architectures))
Example #2
0
def set_default_track(snap_name: str, track_name: str):
    """Set the default track for <snap-name> to <track>.

    The track must be a valid active track for this operation to be successful.
    """
    store_client_cli = StoreClientCLI()

    # Client-side check to verify that the selected track exists.
    snap_channel_map = store_client_cli.get_snap_channel_map(snap_name=snap_name)
    active_tracks = [
        track.name
        for track in snap_channel_map.snap.tracks
        if track.status in ("default", "active")
    ]
    if track_name not in active_tracks:
        echo.exit_error(
            brief=f"The specified track {track_name!r} does not exist for {snap_name!r}.",
            resolution=f"Ensure the {track_name!r} track exists for the {snap_name!r} snap and try again.",
            details="Valid tracks for {!r}: {}.".format(
                snap_name, ", ".join([f"{t!r}" for t in active_tracks])
            ),
        )

    metadata = dict(default_track=track_name)
    store_client_cli.upload_metadata(snap_name=snap_name, metadata=metadata, force=True)

    echo.info(f"Default track for {snap_name!r} set to {track_name!r}.")
Example #3
0
def status(snap_name, architectures, tracks,
           experimental_progressive_releases):
    """Get the status on the store for <snap-name>.

    \b
    Examples:
        snapcraft status my-snap
        snapcraft status --track 20 my-snap
        snapcraft status --arch amd64 my-snap
    """
    if experimental_progressive_releases:
        os.environ["SNAPCRAFT_EXPERIMENTAL_PROGRESSIVE_RELEASES"] = "Y"
        echo.warning("*EXPERIMENTAL* progressive releases in use.")

    snap_channel_map = StoreClientCLI().get_snap_channel_map(
        snap_name=snap_name)
    existing_architectures = snap_channel_map.get_existing_architectures()

    if not snap_channel_map.channel_map:
        echo.warning("This snap has no released revisions.")
    else:
        if architectures:
            architectures = set(architectures)
            for architecture in architectures.copy():
                if architecture not in existing_architectures:
                    echo.warning(
                        f"No revisions for architecture {architecture!r}.")
                    architectures.remove(architecture)

            # If we have no revisions for any of the architectures requested, there's
            # nothing to do here.
            if not architectures:
                return
        else:
            architectures = existing_architectures

        if tracks:
            tracks = set(tracks)
            existing_tracks = {
                s.track
                for s in snap_channel_map.snap.channels if s.track in tracks
            }
            for track in tracks - existing_tracks:
                echo.warning(f"No revisions in track {track!r}.")
            tracks = existing_tracks

            # If we have no revisions in any of the tracks requested, there's
            # nothing to do here.
            if not tracks:
                return
        else:
            tracks = None

        click.echo(
            get_tabulated_channel_map(snap_channel_map,
                                      architectures=architectures,
                                      tracks=tracks))
Example #4
0
def set_default_track(snap_name: str, track_name: str):
    """Set the default track for <snap-name> to <track>.

    The track must be a valid active track for this operation to be successful.
    """
    store_client_cli = StoreClientCLI()
    metadata = dict(default_track=track_name)
    store_client_cli.upload_metadata(snap_name=snap_name,
                                     metadata=metadata,
                                     force=True)

    echo.info(f"Default track for {snap_name!r} set to {track_name!r}.")
Example #5
0
def upload(snap_file, release):
    """Upload <snap-file> to the store.

    By passing --release with a comma separated list of channels the snap would
    be released to the selected channels if the store review passes for this
    <snap-file>.

    This operation will block until the store finishes processing this
    <snap-file>.

    If --release is used, the channel map will be displayed after the
    operation takes place.

    \b
    Examples:
        snapcraft upload my-snap_0.1_amd64.snap
        snapcraft upload my-snap_0.2_amd64.snap --release edge
        snapcraft upload my-snap_0.3_amd64.snap --release candidate,beta
    """
    click.echo("Preparing to upload {!r}.".format(os.path.basename(snap_file)))
    if release:
        channel_list = release.split(",")
        click.echo(
            "After uploading, the resulting snap revision will be released to "
            "{} when it passes the Snap Store review."
            "".format(formatting_utils.humanize_list(channel_list, "and"))
        )
    else:
        channel_list = None

    review_snap(snap_file=snap_file)
    snap_name, snap_revision = snapcraft.upload(snap_file, channel_list)

    echo.info("Revision {!r} of {!r} created.".format(snap_revision, snap_name))
    if channel_list:
        store_client_cli = StoreClientCLI()
        snap_channel_map = store_client_cli.get_snap_channel_map(snap_name=snap_name)

        click.echo(
            get_tabulated_channel_map(
                snap_channel_map,
                architectures=snap_channel_map.get_revision(
                    snap_revision
                ).architectures,
            )
        )
Example #6
0
def list_validation_sets(name, sequence):
    """Get the list of validation sets.

    The sequence option can be a sequence number or a keyword.

    \b
    Examples:
        snapcraft list-validation-sets
        snapcraft list-validation-sets --sequence all
        snapcraft list-validation-sets --name my-set --sequence 1

    Refer to https://snapcraft.io/docs/validation-sets for further information
    on Validation Sets.
    """
    store_client = StoreClientCLI()
    asserted_validation_sets = store_client.get_validation_sets(
        name=name,
        sequence=sequence,
    )

    if not asserted_validation_sets.assertions and (name or sequence):
        echo.warning(
            "No validation sets found for the requested name or sequence.")
    elif not asserted_validation_sets.assertions:
        echo.warning("No validation sets found for this account.")
    else:
        headers = ["Account-ID", "Name", "Sequence", "Revision", "When"]
        assertions = list()
        for assertion in asserted_validation_sets.assertions:
            assertions.append([
                assertion.account_id,
                assertion.name,
                assertion.sequence,
                assertion.revision,
                datetime.strptime(assertion.timestamp,
                                  "%Y-%m-%dT%H:%M:%SZ").strftime("%Y-%m-%d"),
            ])

        click.echo(
            tabulate(assertions,
                     numalign="left",
                     headers=headers,
                     tablefmt="plain"))
Example #7
0
def whoami():
    """Returns your login information relevant to the store."""
    account = StoreClientCLI().whoami().account

    click.echo(
        dedent(
            f"""\
        email:        {account.email}
        developer-id: {account.account_id}"""
        )
    )
Example #8
0
def list_tracks(snap_name: str) -> None:
    """List channel tracks for <snap-name>.

    This command has an alias of `tracks`.

    Track status, creation dates and version patterns are returned alongside
    the track names in a space formatted table.

    Possible Status values are:

    \b
    - active, visible tracks available for installation
    - default, the default track to install from when not explicit
    - hidden, tracks available for installation but unlisted
    - closed, tracks that are no longer available to install from

    A version pattern is a regular expression that restricts a snap revision
    from being released to a track if the version string set does not match.
    """
    store_client_cli = StoreClientCLI()
    snap_channel_map = store_client_cli.get_snap_channel_map(snap_name=snap_name)

    # Iterate over the entries, replace None with - for consistent presentation
    track_table: List[List[str]] = [
        [
            track.name,
            track.status,
            track.creation_date if track.creation_date else "-",
            track.version_pattern if track.version_pattern else "-",
        ]
        for track in snap_channel_map.snap.tracks
    ]

    click.echo(
        tabulate(
            # Sort by "creation-date".
            sorted(track_table, key=operator.itemgetter(2)),
            headers=["Name", "Status", "Creation-Date", "Version-Pattern"],
            tablefmt="plain",
        )
    )
Example #9
0
def whoami():
    """Returns your login information relevant to the store."""
    # TODO: workaround until bakery client is added.
    conf = UbuntuOneSSOConfig()
    email = conf.get("email")
    if email is None:
        email = "unknown"

    account_info = StoreClientCLI().get_account_information()
    account_id = account_info["account_id"]

    click.echo(
        dedent(f"""\
        email:        {email}
        developer-id: {account_id}"""))
Example #10
0
def edit_validation_sets(account_id: str, set_name: str, sequence: int,
                         key_name: str):
    """Edit the list of validations for <set-name>.

    Refer to https://snapcraft.io/docs/validation-sets for further information
    on Validation Sets.
    """
    store_client = StoreClientCLI()

    asserted_validation_sets = store_client.get_validation_sets(
        name=set_name, sequence=str(sequence))

    try:
        # assertions should only have one item since a specific
        # sequence was requested.
        revision = asserted_validation_sets.assertions[0].revision
        snaps = yaml_utils.dump({
            "snaps": [
                s.marshal()
                for s in asserted_validation_sets.assertions[0].snaps
            ]
        })
    except IndexError:
        # If there is no assertion for a given sequence, the store API
        # will return an empty list.
        revision = "0"
        snaps = _VALIDATIONS_SETS_SNAPS_TEMPLATE

    unverified_validation_sets = _VALIDATION_SETS_TEMPLATE.format(
        account_id=account_id,
        set_name=set_name,
        sequence=sequence,
        revision=revision,
        snaps=snaps,
    )

    edited_validation_sets = _edit_validation_sets(unverified_validation_sets)
    if edited_validation_sets == yaml_utils.load(unverified_validation_sets):
        echo.warning("No changes made.")
    else:
        build_assertion = store_client.post_validation_sets_build_assertion(
            validation_sets=edited_validation_sets)
        signed_validation_sets = _sign_assertion(build_assertion.marshal(),
                                                 key_name=key_name)
        store_client.post_validation_sets(
            signed_validation_sets=signed_validation_sets)
Example #11
0
def list_revisions(snap_name, arch):
    """Get the history on the store for <snap-name>.

    This command has an alias of `revisions`.

    \b
    Examples:
        snapcraft list-revisions my-snap
        snapcraft list-revisions my-snap --arch armhf
        snapcraft revisions my-snap
    """
    releases = StoreClientCLI().get_snap_releases(snap_name=snap_name)

    def get_channels_for_revision(revision: int) -> List[str]:
        # channels: the set of channels revision was released to, active or not.
        channels: Set[str] = set()
        # seen_channel: applies to channels regardless of revision.
        # The first channel that shows up for each architecture is to
        # be marked as the active channel, all others are historic.
        seen_channel: Dict[str, Set[str]] = dict()

        for release in releases.releases:
            if release.architecture not in seen_channel:
                seen_channel[release.architecture] = set()

            # If the revision is in this release entry and was not seen
            # before it means that this channel is active and needs to
            # be represented with a *.
            if (
                release.revision == revision
                and release.channel not in seen_channel[release.architecture]
            ):
                channels.add(f"{release.channel}*")
            # All other releases found for a revision are inactive.
            elif (
                release.revision == revision
                and release.channel not in channels
                and f"{release.channel}*" not in channels
            ):
                channels.add(release.channel)

            seen_channel[release.architecture].add(release.channel)

        return sorted(list(channels))

    parsed_revisions = list()
    for rev in releases.revisions:
        if arch and arch not in rev.architectures:
            continue
        channels_for_revision = get_channels_for_revision(rev.revision)
        if channels_for_revision:
            channels = ",".join(channels_for_revision)
        else:
            channels = "-"
        parsed_revisions.append(
            (
                rev.revision,
                rev.created_at,
                ",".join(rev.architectures),
                rev.version,
                channels,
            )
        )

    tabulated_revisions = tabulate(
        parsed_revisions,
        numalign="left",
        headers=["Rev.", "Uploaded", "Arches", "Version", "Channels"],
        tablefmt="plain",
    )

    # 23 revisions + header should not need paging.
    if len(parsed_revisions) < 24:
        click.echo(tabulated_revisions)
    else:
        click.echo_via_pager(tabulated_revisions)
Example #12
0
def release(
    snap_name,
    revision,
    channels,
    progressive: Optional[int],
    experimental_progressive_releases: bool,
) -> None:
    """Release <snap-name> on <revision> to the selected store <channels>.
    <channels> is a comma separated list of valid channels on the
    store.

    The <revision> must exist on the store, to see available revisions
    run `snapcraft list-revisions <snap_name>`.

    The channel map will be displayed after the operation takes place.
    To see the status map at any other time run `snapcraft status <snap-name>`.

    The format for channels is `[<track>/]<risk>[/<branch>]` where

    \b
        - <track> is used to have long term release channels. It is implicitly
          set to `latest`. If this snap requires one, it can be created by
          request by having a conversation on https://forum.snapcraft.io
          under the *store* category.
        - <risk> is mandatory and can be either `stable`, `candidate`, `beta`
          or `edge`.
        - <branch> is optional and dynamically creates a channel with a
          specific expiration date.

    \b
    Examples:
        snapcraft release my-snap 8 stable
        snapcraft release my-snap 8 stable/my-branch
        snapcraft release my-snap 9 beta,edge
        snapcraft release my-snap 9 lts-channel/stable
        snapcraft release my-snap 9 lts-channel/stable/my-branch
    """
    # If progressive is set to 100, treat it as None.
    if progressive == 100:
        progressive = None

    if progressive is not None and not experimental_progressive_releases:
        raise click.UsageError(
            "--progressive requires --experimental-progressive-releases."
        )
    elif progressive:
        os.environ["SNAPCRAFT_EXPERIMENTAL_PROGRESSIVE_RELEASES"] = "Y"
        echo.warning("*EXPERIMENTAL* progressive releases in use.")

    store_client_cli = StoreClientCLI()
    release_data = store_client_cli.release(
        snap_name=snap_name,
        revision=revision,
        channels=channels.split(","),
        progressive_percentage=progressive,
    )
    snap_channel_map = store_client_cli.get_snap_channel_map(snap_name=snap_name)
    architectures_for_revision = snap_channel_map.get_revision(
        int(revision)
    ).architectures
    tracks = [storeapi.channels.Channel(c).track for c in channels.split(",")]
    click.echo(
        get_tabulated_channel_map(
            snap_channel_map, tracks=tracks, architectures=architectures_for_revision
        )
    )

    opened_channels = release_data.get("opened_channels", [])
    if len(opened_channels) == 1:
        echo.info(f"The {opened_channels[0]!r} channel is now open.")
    elif len(opened_channels) > 1:
        channels = ("{!r}".format(channel) for channel in opened_channels[:-1])
        echo.info(
            "The {} and {!r} channels are now open.".format(
                ", ".join(channels), opened_channels[-1]
            )
        )