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))
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}.")
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))
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}.")
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, ) )
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"))
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}""" ) )
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", ) )
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}"""))
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)
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)
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] ) )