Michal Marek bed3c7
package SUSE::MyBS;
Michal Marek bed3c7
Michal Marek bed3c7
use strict;
Michal Marek bed3c7
use warnings;
Michal Marek bed3c7
Michal Marek bed3c7
use Carp;
Michal Marek bed3c7
use LWP::UserAgent;
Michal Marek bed3c7
use URI;
Michal Marek bed3c7
use XML::Parser;
Michal Marek bed3c7
use XML::Writer;
Michal Marek bed3c7
use HTTP::Request;
Michal Marek bed3c7
use File::Temp qw(tempfile);
Michal Marek bed3c7
use Config::IniFiles;
Michal Marek bed3c7
use Digest::MD5;
Michal Marek bed3c7
Michal Marek bed3c7
use SUSE::MyBS::Buildresults;
Michal Marek bed3c7
Michal Marek bed3c7
sub new {
Michal Marek bed3c7
	my ($class, $api_url) = @_;
Michal Marek bed3c7
	my $self = { };
Michal Marek bed3c7
Michal Marek bed3c7
	$api_url ||= "https://api.opensuse.org";
Michal Marek bed3c7
	$api_url =~ s:/*$::;
Michal Marek bed3c7
	if ($api_url !~ m@^https?://@) {
Michal Marek bed3c7
		$api_url = "https://$api_url";
Michal Marek bed3c7
	}
Michal Marek bed3c7
	$self->{url} = URI->new($api_url);
Michal Marek bed3c7
Jiri Slaby cd4eb9
	my $cfgfile;
Jiri Slaby cd4eb9
	foreach ("$ENV{HOME}/.oscrc", "$ENV{HOME}/.config/osc/oscrc") {
Jiri Slaby cd4eb9
		if (-f) {
Jiri Slaby cd4eb9
			$cfgfile = $_;
Jiri Slaby cd4eb9
			last;
Jiri Slaby cd4eb9
		}
Jiri Slaby cd4eb9
	}
Jiri Slaby cd4eb9
Jiri Slaby cd4eb9
	defined $cfgfile or die "oscrc not found";
Jiri Slaby cd4eb9
Michal Marek bed3c7
	# replace name: value with name= value that Config::IniFiles can parse
Michal Marek bed3c7
	open(my $fh, '<', $cfgfile) or die "$cfgfile: $!\n";
Michal Marek bed3c7
	my $data = "";
Michal Marek bed3c7
	my $changed = 0;
Michal Marek bed3c7
	while (<$fh>) {
Michal Marek bed3c7
		if (s/^([^[:=]+):/$1=/) {
Michal Marek bed3c7
			$changed = 1;
Michal Marek bed3c7
		}
Michal Marek bed3c7
		$data .= $_;
Michal Marek bed3c7
	}
Michal Marek bed3c7
	close($fh);
Michal Marek bed3c7
	my $config;
Michal Marek bed3c7
	if ($changed) {
Michal Marek bed3c7
		(my $fh, $cfgfile) = tempfile();
Michal Marek bed3c7
		print $fh $data;
Michal Marek bed3c7
		close($fh);
Michal Marek bed3c7
	}
Michal Marek bed3c7
	my %config;
Michal Marek bed3c7
	tie %config, 'Config::IniFiles', (-file => $cfgfile);
Michal Marek bed3c7
	if ($changed) {
Michal Marek bed3c7
		unlink($cfgfile);
Michal Marek bed3c7
	}
Michal Marek bed3c7
	if (!keys(%config)) {
Michal Marek bed3c7
		die join("\n", @Config::IniFiles::errors), "\n";
Michal Marek bed3c7
	}
Michal Marek bed3c7
	my %cred;
Michal Suchanek 5f57bd
	for my $kw (qw(user pass passx keyring credentials_mgr_class)) {
Michal Marek bed3c7
		for my $section ($api_url, "$api_url/", $self->{url}->host) {
Michal Marek bed3c7
			if (exists($config{$section}) &&
Michal Marek bed3c7
					exists($config{$section}{$kw})) {
Michal Marek bed3c7
				$cred{$kw} = $config{$section}{$kw};
Michal Marek bed3c7
				last;
Michal Marek bed3c7
			}
Michal Marek bed3c7
		}
Michal Marek bed3c7
	}
Michal Suchanek 47ccb9
	if (exists($cred{credentials_mgr_class})) {
Michal Suchanek 47ccb9
		if ($cred{credentials_mgr_class} eq "osc.credentials.ObfuscatedConfigFileCredentialsManager") {
Michal Suchanek 47ccb9
			$cred{passx}=$cred{pass};
Michal Suchanek 47ccb9
	}}
Michal Suchanek 5f57bd
	if (exists($cred{passx})) {
Michal Marek bed3c7
		# Not available on SLES10, hence the 'require'
Michal Marek bed3c7
		require MIME::Base64;
Michal Marek bed3c7
		require IO::Uncompress::Bunzip2;
Michal Marek bed3c7
		my $bz2 = MIME::Base64::decode_base64($cred{passx});
Michal Marek bed3c7
		$cred{pass} = "";
Michal Marek bed3c7
		IO::Uncompress::Bunzip2::bunzip2(\$bz2 => \$cred{pass})
Michal Marek bed3c7
			or die "Decoding password for $api_url failed: $IO::Uncompress::Bunzip2::Bunzip2Error\n";
Michal Marek bed3c7
	}
4bafc0
	if (!exists($cred{pass}) && exists($cred{keyring})) {
d2378a
		my $api = $api_url;
d2378a
		$api =~ s/^https?:\/\///;
d2378a
		open(my $secret, "secret-tool lookup service $api username $cred{user} |")
d2378a
		    or die "Please install the \"secret-tool\" package to use a keyring\n";
4bafc0
		$cred{pass} = <$secret>;
4bafc0
		close($secret);
d2378a
		die "Failed to obtain secret from secret-tool\n"
4bafc0
		    if !$cred{pass};
4bafc0
		chomp($cred{pass});
Michal Suchanek 5f57bd
	}
Michal Suchanek 5f57bd
	if (!exists($cred{user}) || !exists($cred{pass})) {
Michal Suchanek 5f57bd
			die "Error: Username or password for $api_url not set in ~/.oscrc\n" .
Michal Suchanek 5f57bd
			"Error: Run `osc -A $api_url ls' once\n";
4bafc0
	}
Michal Marek bed3c7
Michal Marek bed3c7
	$self->{ua} = LWP::UserAgent->new;
Michal Suchanek 3ae8f5
	my $realm = "Use your developer account";
Jiri Slaby 54326b
	$realm = "Use your SUSE developer account" if $api_url =~ /opensuse/;
Michal Suchanek 3ae8f5
	$self->{ua}->credentials($self->{url}->host_port, $realm,
Michal Marek bed3c7
		$cred{user}, $cred{pass});
Michal Marek bed3c7
	if ($self->{ua}->can('ssl_opts')) {
Michal Marek bed3c7
		$self->{ua}->ssl_opts(verify_hostname => 1);
Michal Marek bed3c7
	}
Michal Marek bed3c7
Michal Marek bed3c7
	bless($self, $class);
Michal Marek bed3c7
Michal Marek bed3c7
	if ($self->{url}->scheme eq "https" &&
Michal Marek bed3c7
				$self->{url}->host eq "api.suse.de" &&
Michal Marek bed3c7
				$self->{ua}->can('ssl_opts')) {
Michal Marek bed3c7
		eval {
Michal Marek bed3c7
			$self->get("/about");
Michal Marek bed3c7
		};
Michal Marek bed3c7
		if ($@) {
Michal Marek bed3c7
			# Use the canned certificate as a backup
Michal Marek bed3c7
			# XXX: Check that we really got an unknown cert error
Michal Marek bed3c7
			(my $pkg = __PACKAGE__) =~ s@::@/@g;
Michal Marek bed3c7
			$pkg .= ".pm";
Michal Marek bed3c7
			(my $cert = $INC{$pkg}) =~ s@[^/]*$@@;
Michal Marek bed3c7
			$cert .= "SUSE_Trust_Root.pem";
Michal Marek bed3c7
			$self->{ua}->ssl_opts(SSL_ca_file => $cert);
Michal Marek bed3c7
		}
Michal Marek bed3c7
	}
Michal Marek bed3c7
	return $self;
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub api {
Michal Marek bed3c7
	my ($self, $method, $path, $data) = @_;
Michal Marek bed3c7
	my $url = $self->{url} . $path;
Michal Marek bed3c7
Michal Marek bed3c7
	my $req = HTTP::Request->new($method => $url);
Michal Marek bed3c7
	if ($data) {
Michal Marek bed3c7
		$req->add_content($data);
Michal Marek 2cc93a
		$req->header("Content-type" => "application/octet-stream");
Michal Marek bed3c7
	}
Michal Marek 4dbe92
	#$self->{ua}->prepare_request($req);
Michal Marek 4dbe92
	#print STDERR "req: " . $req->as_string() . "\n";
Michal Suchanek 3ae8f5
	#$self->{ua}->add_handler(request_send => sub { my($req, $ua, $handler) = @_; print STDERR "req: " . $req->as_string() . "\n"; return; m_method => "GET"});
Michal Marek bed3c7
	my $res = $self->{ua}->request($req);
Michal Marek bed3c7
	if ($res->code != 200) {
Michal Marek 4dbe92
		#print STDERR $res->as_string();
Michal Marek 4b77a5
		die "$method $path: @{[$res->message()]} (HTTP @{[$res->code()]})\n";
Michal Marek bed3c7
	}
Michal Marek bed3c7
	return $res->content();
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub get {
Michal Marek bed3c7
	my $self = shift;
Michal Marek bed3c7
Michal Marek bed3c7
	$self->api('GET', @_);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub post {
Michal Marek bed3c7
	my $self = shift;
Michal Marek bed3c7
Michal Marek bed3c7
	$self->api('POST', @_);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub put {
Michal Marek bed3c7
	my $self = shift;
Michal Marek bed3c7
Michal Marek bed3c7
	$self->api('PUT', @_);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub put_file {
Michal Marek bed3c7
	my ($self, $file, $path) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	open(my $fh, '<', $file) or die "$file: $!\n";
Michal Marek bed3c7
	local $/ = undef;
Michal Marek bed3c7
	my $data = <$fh>;
Michal Marek bed3c7
	close($fh);
Michal Marek bed3c7
	$self->put($path, $data);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub delete {
Michal Marek bed3c7
	my $self = shift;
Michal Marek bed3c7
Michal Marek bed3c7
	$self->api('DELETE', @_);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub exists {
Michal Marek bed3c7
	my ($self, $path) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	eval {
Michal Marek bed3c7
		$self->get($path);
Michal Marek bed3c7
	};
Michal Marek bed3c7
	return 0 if $@;
Michal Marek bed3c7
	return 1;
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub project_exists {
Michal Marek bed3c7
	my ($self, $project) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	return $self->exists("/source/$project/_meta");
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub package_exists {
Michal Marek bed3c7
	my ($self, $project, $package) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	return $self->exists("/source/$project/$package/_meta");
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub readdir {
Michal Marek bed3c7
	my ($self, $path) = @_;
Michal Marek bed3c7
	my $xml = $self->get($path);
Michal Marek bed3c7
Michal Marek bed3c7
	my $handle_start = sub  {
Michal Marek bed3c7
		my ($self, $element, %attr) = @_;
Michal Marek bed3c7
		return unless $element eq "entry" || $element eq "binary";
Michal Marek bed3c7
		my $name;
Michal Marek bed3c7
		if ($attr{name}) {
Michal Marek bed3c7
			$name = $attr{name};
Michal Marek bed3c7
			delete($attr{name});
Michal Marek bed3c7
		} elsif ($attr{filename}) {
Michal Marek bed3c7
			$name = $attr{filename};
Michal Marek bed3c7
			delete($attr{filename});
Michal Marek bed3c7
		}
Michal Marek bed3c7
		$self->{res}->{$name} = \%attr;
Michal Marek bed3c7
	};
Michal Marek bed3c7
Michal Marek bed3c7
	my $p = XML::Parser->new(Handlers => {Start => $handle_start});
Michal Marek bed3c7
	$p->{res} = {};
Michal Marek bed3c7
	$p->parse($xml);
Michal Marek bed3c7
	return $p->{res};
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
# Return list of architectures for given repository of given project
Michal Marek bed3c7
sub get_repo_archs {
Michal Marek bed3c7
	my ($self, $project, $repository) = @_;
Michal Marek bed3c7
	my $xml = $self->get("/source/$project/_meta");
Michal Marek bed3c7
Michal Marek bed3c7
	my $handle_start = sub  {
Michal Marek bed3c7
		my ($self, $element, %attr) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
		return if $element ne "repository";
Michal Marek bed3c7
		if (defined($repository)) {
Michal Marek bed3c7
			return if $attr{name} ne $repository;
Michal Marek bed3c7
		}
Michal Marek 126229
		if ($attr{name} eq "standard" ||
Michal Marek 011b66
		    $attr{name} eq "ports" && $project !~ /\bopenSUSE:Factory\b/ ||
Michal Marek 126229
		    $attr{name} =~ /^SUSE_.*_Update$/ && $project =~ /^SUSE:Maintenance:/) {
Michal Marek 126229
			$self->{has_match} = 1;
Michal Marek 126229
			$self->{repo_name} = $attr{name};
Michal Marek 126229
			$self->{res}{$attr{name}} ||= [];
Michal Marek 126229
		}
Michal Marek bed3c7
	};
Michal Marek bed3c7
	my $handle_char = sub {
Michal Marek bed3c7
		my ($self, $string) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
		if ($self->{has_match}) {
Michal Marek bed3c7
			$string =~ s/\s//g;
Michal Marek bed3c7
			$self->{cur_string} .= $string;
Michal Marek bed3c7
		}
Michal Marek bed3c7
	};
Michal Marek bed3c7
	my $handle_end = sub {
Michal Marek bed3c7
		my ($self, $element) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
		if ($element eq "repository") {
Michal Marek bed3c7
			$self->{has_match} = 0;
Michal Marek bed3c7
		}
Michal Marek bed3c7
		if ($element eq "arch" && $self->{has_match}) {
Michal Marek bed3c7
			push(@{$self->{res}{$self->{repo_name}}}, $self->{cur_string});
Michal Marek bed3c7
			$self->{cur_string} = "";
Michal Marek bed3c7
		}
Michal Marek bed3c7
	};
Michal Marek bed3c7
	my $p = XML::Parser->new(Handlers => {
Michal Marek bed3c7
			Start => $handle_start,
Michal Marek bed3c7
			Char => $handle_char,
Michal Marek bed3c7
			End => $handle_end});
Michal Marek bed3c7
	$p->{res} = {};
Michal Marek bed3c7
	$p->parse($xml);
Michal Marek bed3c7
	return %{$p->{res}};
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub create_project {
Michal Marek bed3c7
	my ($self, $project, $options) = @_;
Michal Marek bed3c7
	my %limit_archs;
Michal Marek bed3c7
Michal Marek bed3c7
	$options->{title} ||= $project,
Michal Marek bed3c7
	$options->{description} ||= "";
Michal Marek bed3c7
	if (!exists($options->{build})) {
Michal Marek bed3c7
		$options->{build} = 1;
Michal Marek bed3c7
	}
Michal Marek bed3c7
	if (!exists($options->{publish})) {
Michal Marek bed3c7
		$options->{publish} = 1;
Michal Marek bed3c7
	}
Michal Marek bed3c7
	$options->{limit_archs} ||= [];
Michal Marek bed3c7
	if (scalar(@{$options->{limit_archs}})) {
Michal Marek bed3c7
		$limit_archs{$_} = 1 for @{$options->{limit_archs}};
Michal Marek bed3c7
	} else {
Michal Marek bed3c7
		$options->{limit_archs} = undef;
Michal Marek bed3c7
	}
Michal Marek bed3c7
Michal Marek bed3c7
	my $meta;
Michal Marek bed3c7
	my $writer = XML::Writer->new(OUTPUT => \$meta);
Michal Marek bed3c7
	$writer->startTag("project", name => $project);
Michal Marek bed3c7
	$writer->dataElement("title", $options->{title});
Michal Marek bed3c7
	$writer->dataElement("description", $options->{description});
Michal Marek bed3c7
	$options->{maintainers} ||= [];
Michal Marek bed3c7
	for my $m (@{$options->{maintainers}}) {
Michal Marek bed3c7
		if ($self->exists("/group/$m")) {
Michal Marek bed3c7
			$writer->emptyTag("group", groupid => $m,
Michal Marek bed3c7
				role => "maintainer");
Michal Marek bed3c7
		} elsif ($self->exists("/person/$m")) {
Michal Marek bed3c7
			$writer->emptyTag("person", userid => $m,
Michal Marek bed3c7
				role => "maintainer");
Michal Marek bed3c7
		} else {
Michal Marek bed3c7
			warn("User id $m does not exist at $self->{url}\n");
Michal Marek bed3c7
		}
Michal Marek bed3c7
	}
Michal Marek bed3c7
Michal Marek bed3c7
	if (!exists($options->{repos})) {
Michal Marek bed3c7
		if (!exists($options->{base})) {
Michal Marek bed3c7
			croak "Either 'base' or 'repos' must be specified";
Michal Marek bed3c7
		}
Michal Marek bed3c7
		$options->{repository} ||= "";
Michal Marek bed3c7
		$options->{repos} = { $options->{repository} => $options->{base} };
Michal Marek bed3c7
	}
Michal Marek bed3c7
	my %seen_archs;
Michal Marek 3b4784
	my @qa_repos;
Michal Marek bed3c7
	for my $repo (sort(keys(%{$options->{repos}}))) {
Michal Marek bed3c7
		my $base = $options->{repos}{$repo};
Michal Marek bed3c7
		my %repo_archs;
Michal Marek bed3c7
		if ($repo eq "") {
Michal Marek bed3c7
			# get all "default" repositories of a given project
Michal Marek bed3c7
			%repo_archs = $self->get_repo_archs($base);
Michal Marek bed3c7
		} else {
Michal Marek bed3c7
			# get the "standard" repository of a given project
Michal Marek bed3c7
			%repo_archs = $self->get_repo_archs($base, "standard");
Michal Marek bed3c7
		}
Michal Marek bed3c7
		for my $r (sort(keys(%repo_archs))) {
Michal Marek 3b4784
			my $name = $repo ? $repo : $r;
Michal Marek 3b4784
			my @attrs = (name => $name);
Michal Marek 2f6d58
			if (!$options->{rebuild}) {
Michal Marek 2f6d58
				push(@attrs, rebuild => "local", block => "local");
Michal Marek 2f6d58
			}
Michal Marek 3b4784
			my @archs;
Michal Marek bed3c7
			for my $arch (@{$repo_archs{$r}}) {
Michal Marek bed3c7
				if ($options->{limit_archs} &&
Michal Marek bed3c7
					!$limit_archs{$arch}) {
Michal Marek bed3c7
					next;
Michal Marek bed3c7
				}
Michal Marek bed3c7
				# only build each arch once
Michal Marek bed3c7
				if ($seen_archs{$arch}) {
Michal Marek bed3c7
					next;
Michal Marek bed3c7
				}
Michal Marek bed3c7
				$seen_archs{$arch} = 1;
Michal Marek 3b4784
				push(@archs, $arch);
Michal Marek 3b4784
			}
Michal Marek 31029c
			if (!@archs) {
Michal Marek 31029c
				# this repository is not needed
Michal Marek 31029c
				next;
Michal Marek 31029c
			}
Michal Marek 3b4784
			$writer->startTag("repository", @attrs);
Michal Marek 3b4784
			$writer->emptyTag("path", repository => $r,
Michal Marek 3b4784
				project => $base);
Michal Marek 3b4784
			for my $arch (@archs) {
Michal Marek bed3c7
				$writer->dataElement("arch", $arch);
Michal Marek bed3c7
			}
Michal Marek bed3c7
			$writer->endTag("repository");
Michal Marek 3b4784
			if (!exists($options->{qa})) {
Michal Marek 3b4784
				next;
Michal Marek 3b4784
			}
Michal Marek 3b4784
			# For each regular repository foo, there is a
Michal Marek 3b4784
			# repository named QA_foo, building against foo
Michal Marek 3b4784
			my $qa_name = ($name eq "standard") ? "QA"
Michal Marek 3b4784
					: "QA_$name";
Michal Marek 3b4784
			$writer->startTag("repository", name => $qa_name);
Michal Marek 3b4784
			$writer->emptyTag("path", repository => $name,
Michal Marek bed3c7
				project => $project);
Michal Marek 3b4784
			for my $arch (@archs) {
Michal Marek 114d5b
				$writer->dataElement("arch", $arch);
Michal Marek bed3c7
			}
Michal Marek 114d5b
			$writer->endTag("repository");
Michal Marek 3b4784
			push(@qa_repos, $qa_name);
Michal Marek bed3c7
		}
Michal Marek bed3c7
	}
Michal Marek 3b4784
	for my $attr (qw(build publish debuginfo)) {
Michal Marek 3b4784
		$writer->startTag($attr);
Michal Marek 3b4784
		$writer->emptyTag($options->{$attr} ? "enable" : "disable");
Michal Marek 3b4784
		if ($attr =~ /^(publish|build)/) {
Michal Marek 3b4784
			for my $repo (@qa_repos) {
Michal Marek 3b4784
				$writer->emptyTag("disable", repository => $repo);
Michal Marek 3b4784
			}
Michal Marek 3b4784
		}
Michal Marek 3b4784
		$writer->endTag($attr);
Michal Marek 3b4784
	}
Michal Marek 3b4784
Michal Marek bed3c7
	$writer->endTag("project");
Michal Marek bed3c7
	$writer->end();
Michal Marek bed3c7
Michal Marek bed3c7
	$self->put("/source/$project/_meta?force=1", $meta);
Michal Marek bed3c7
	my $prjconf = "";
Michal Marek bed3c7
	if ($options->{prjconf}) {
Michal Marek bed3c7
		$prjconf .= $options->{prjconf};
Michal Marek bed3c7
	}
Michal Marek bed3c7
	for my $package (@{$options->{remove_packages} || []}) {
Michal Marek bed3c7
		# OBS idiom: substitute the package by an empty set
Michal Marek bed3c7
		$prjconf .= "Substitute: $package\n";
Michal Marek bed3c7
	}
Michal Marek bed3c7
	for my $package (@{$options->{add_packages} || []}) {
Michal Marek bed3c7
		$prjconf .= "Support: $package\n";
Michal Marek bed3c7
	}
Michal Marek bed3c7
	$prjconf .= "Macros:\n";
Michal Marek bed3c7
	for my $macro (@{$options->{macros} || []}) {
Michal Marek bed3c7
		$prjconf .= "$macro\n";
Michal Marek bed3c7
	}
Olaf Hering 965157
	$prjconf .= ":Macros\n";
Michal Marek bed3c7
	$self->put("/source/$project/_config", $prjconf);
Michal Marek 3b4784
	return { name => $project, qa_repos => \@qa_repos };
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub create_package {
Michal Marek 3b4784
	my ($self, $prj, $package, $title, $description) = @_;
Michal Marek bed3c7
	$title ||= $package;
Michal Marek bed3c7
	$description ||= "";
Michal Marek bed3c7
Michal Marek bed3c7
	my $meta;
Michal Marek bed3c7
	my $writer = XML::Writer->new(OUTPUT => \$meta);
Michal Marek 3b4784
	$writer->startTag("package", project => $prj->{name}, name => $package);
Michal Marek bed3c7
	$writer->dataElement("title", $title);
Michal Marek bed3c7
	$writer->dataElement("description", $description);
Michal Marek bed3c7
	# XXX: HACK
Michal Marek a200b4
	if ($package =~ /^kernel-obs-(qa|build)/) {
Michal Marek bed3c7
		$writer->startTag("build");
Michal Marek bed3c7
		$writer->emptyTag("disable");
Michal Marek 3b4784
		for my $repo (@{$prj->{qa_repos} || []}) {
Michal Marek 3b4784
			$writer->emptyTag("enable", repository => $repo);
Michal Marek 3b4784
		}
Michal Marek bed3c7
		$writer->endTag("build");
Michal Marek bed3c7
	}
Michal Marek bed3c7
	$writer->endTag("package");
Michal Marek bed3c7
	$writer->end();
Michal Marek bed3c7
Michal Marek 3b4784
	$self->put("/source/$prj->{name}/$package/_meta", $meta);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
# Get a list of links to this package within the same project
Michal Marek bed3c7
sub local_links {
Michal Marek bed3c7
	my ($self, $project, $package) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	my $xml = $self->post("/source/$project/$package?cmd=showlinked");
Michal Marek bed3c7
Michal Marek bed3c7
	my $handle_start = sub  {
Michal Marek bed3c7
		my ($self, $element, %attr) = @_;
Michal Marek bed3c7
		return unless $element eq "package";
Michal Marek bed3c7
		return unless exists($attr{project}) && exists($attr{name});
Michal Marek bed3c7
		if ($attr{project} eq $project && $attr{name} ne $package) {
Michal Marek bed3c7
			push(@{$self->{res}}, $attr{name});
Michal Marek bed3c7
		}
Michal Marek bed3c7
	};
Michal Marek bed3c7
Michal Marek bed3c7
	my $p = XML::Parser->new(Handlers => {Start => $handle_start});
Michal Marek bed3c7
	$p->{res} = [];
Michal Marek bed3c7
	$p->parse($xml);
Michal Marek bed3c7
	return @{$p->{res}};
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub get_directory_revision {
Michal Marek bed3c7
	my ($self, $xml) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	my $handle_start = sub {
Michal Marek bed3c7
		my ($self, $element, %attr) = @_;
Michal Marek bed3c7
		return unless $element eq "directory";
Michal Marek bed3c7
		$self->{res}[0] = $attr{rev};
Michal Marek bed3c7
	};
Michal Marek bed3c7
	my $p = XML::Parser->new(Handlers => {Start => $handle_start});
Michal Marek bed3c7
	$p->{res} = [];
Michal Marek bed3c7
	$p->parse($xml);
Michal Marek bed3c7
	return $p->{res}[0];
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub upload_package {
Michal Marek 3b4784
	my ($self, $dir, $prj, $package, $commit, $options) = @_;
Michal Marek bed3c7
	$options ||= {};
Michal Marek bed3c7
	my $progresscb = $options->{progresscb} || sub { };
Michal Marek 7f1a60
	my $no_init = $options->{no_init};
Michal Marek bed3c7
	my $remove_packages = $options->{remove_packages} || [];
Michal Marek bed3c7
	my %remove_packages = map { $_ => 1 } @$remove_packages;
Michal Marek 578383
	my $limit_packages = $options->{limit_packages} || [];
Michal Marek 578383
	my %limit_packages = map { $_ => 1 } @$limit_packages;
Michal Marek 578383
	my $do_limit_packages = (scalar(@$limit_packages) > 0);
Michal Marek 9e95b2
	my $extra_links = $options->{extra_links} || [];
Michal Marek 9e95b2
	my %specfiles = map { $_ => 1 } @$extra_links;
Michal Marek bed3c7
	my $revision;
Michal Marek 3b4784
	if (!ref($prj)) {
Michal Marek 3b4784
		$prj = { name => $prj };
Michal Marek 3b4784
	}
Michal Marek 3b4784
	my $project = $prj->{name};
Michal Marek bed3c7
Michal Marek bed3c7
	if (!$self->project_exists($project)) {
Michal Marek bed3c7
		die "Project $project does not exist\n";
Michal Marek bed3c7
	}
Michal Marek 7f1a60
	if (!$no_init) {
Michal Marek 7f1a60
		$self->create_package($prj, $package);
Michal Marek 7f1a60
		&$progresscb('CREATE', "$project/$package");
Michal Marek 7f1a60
	}
Michal Marek bed3c7
	opendir(my $dh, $dir) or die "$dir: $!\n";
Michal Marek bed3c7
	my $remote = $self->readdir("/source/$project/$package");
Michal Marek bed3c7
	my $new_filelist = "";
Michal Marek bed3c7
	my $filelist_writer = XML::Writer->new(OUTPUT => \$new_filelist);
Michal Marek bed3c7
	$filelist_writer->startTag("directory");
Michal Marek bed3c7
	my $changed = 0;
Michal Marek bed3c7
	while ((my $name = CORE::readdir($dh))) {
Michal Marek bed3c7
		my $local_path = "$dir/$name";
Michal Marek bed3c7
		my $remote_path = "/source/$project/$package/$name?rev=repository";
Michal Marek bed3c7
		next if $name =~ /^\./;
Michal Marek bed3c7
		next if ! -f $local_path;
Michal Marek bed3c7
		open(my $fh, '<', "$dir/$name") or die "$dir/$name: $!\n";
Michal Marek bed3c7
		my $md5 = Digest::MD5->new->addfile($fh)->hexdigest;
Michal Marek bed3c7
		$filelist_writer->emptyTag("entry", name => $name, md5 => $md5);
Michal Marek bed3c7
		if (!$remote->{$name} || $md5 ne $remote->{$name}->{md5}) {
Michal Marek bed3c7
			$self->put_file($local_path, $remote_path);
Michal Marek bed3c7
			&$progresscb('PUT', $name);
Michal Marek bed3c7
			$changed = 1;
Michal Marek bed3c7
		}
Michal Marek bed3c7
		if ($remote->{$name}) {
Michal Marek bed3c7
			delete $remote->{$name};
Michal Marek bed3c7
		}
Michal Marek bed3c7
		if ($name =~ /(.*)\.spec$/) {
Michal Marek bed3c7
			if ($1 ne $package) {
Michal Marek bed3c7
				$specfiles{$1} = 1;
Michal Marek bed3c7
			}
Michal Marek bed3c7
		}
Michal Marek bed3c7
	}
Michal Marek bed3c7
	closedir($dh);
Michal Marek bed3c7
	for my $name (keys(%$remote)) {
Michal Marek bed3c7
		$self->delete("/source/$project/$package/$name");
Michal Marek bed3c7
		&$progresscb('DELETE', $name);
Michal Marek bed3c7
		$changed = 1;
Michal Marek bed3c7
	}
Michal Marek bed3c7
	$filelist_writer->endTag("directory");
Michal Marek bed3c7
	$filelist_writer->end();
Michal Marek bed3c7
	if ($changed) {
Michal Marek bed3c7
		my $xml = $self->post("/source/$project/$package?comment=$commit&cmd=commitfilelist", $new_filelist);
Michal Marek bed3c7
		$revision = $self->get_directory_revision($xml);
Michal Marek bed3c7
	}
Michal Marek 7f1a60
	if ($no_init) {
Michal Marek 7f1a60
		return $revision;
Michal Marek 7f1a60
	}
Michal Marek bed3c7
Michal Marek bed3c7
	# Create links for all specfiles in this package
Michal Marek bed3c7
	my %links = map { $_ => 1 } $self->local_links($project, $package);
Michal Marek bed3c7
	my $link_xml;
Michal Marek bed3c7
	my $writer = XML::Writer->new(OUTPUT => \$link_xml);
Michal Marek bed3c7
	$writer->emptyTag("link", project => $project, package => $package,
Michal Marek bed3c7
		cicount => "copy");
Michal Marek bed3c7
	$writer->end();
Michal Marek bed3c7
Michal Marek bed3c7
	for my $spec (keys(%specfiles)) {
Michal Marek bed3c7
		next if $remove_packages{$spec};
Michal Marek 578383
		next if $do_limit_packages && !$limit_packages{$spec};
Michal Marek 3b4784
		$self->create_package($prj, $spec);
Michal Marek bed3c7
		$self->put("/source/$project/$spec/_link", $link_xml);
Michal Marek bed3c7
		&$progresscb('LINK', "$project/$spec");
Michal Marek bed3c7
		delete($links{$spec});
Michal Marek bed3c7
	}
Michal Marek bed3c7
	# delete stale links
Michal Marek bed3c7
	for my $link (keys(%links)) {
Michal Marek bed3c7
		$self->delete("/source/$project/$link");
Michal Marek bed3c7
		&$progresscb('DELETE', "$project/$link");
Michal Marek bed3c7
	}
Michal Suchanek c5a310
	# delete stale kernel-obs-build
Michal Suchanek c5a310
	my $kob = "kernel-obs-build";
Michal Suchanek c5a310
	$self->post("/build/$project?cmd=wipe&package=$kob");
Michal Suchanek c5a310
	&$progresscb('WIPE', "$project $kob");
Michal Marek bed3c7
	return $revision;
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub submit_package {
Michal Marek bed3c7
	my ($self, $project, $package, $revision, $target, $comment) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	my $request;
Michal Marek bed3c7
	my $writer = XML::Writer->new(OUTPUT => \$request);
Michal Marek bed3c7
	$writer->startTag("request", type => "submit");
Michal Marek bed3c7
		$writer->startTag("submit");
Michal Marek bed3c7
			$writer->emptyTag("source",
Michal Marek bed3c7
				project => $project, package => $package,
Michal Marek bed3c7
				rev => $revision);
Michal Marek bed3c7
			$writer->emptyTag("target",
Michal Marek bed3c7
				project => $target, package => $package);
Michal Marek bed3c7
		$writer->endTag("submit");
Michal Marek bed3c7
		$writer->emptyTag("state", name => "new");
Michal Marek bed3c7
		$writer->dataElement("description", $comment);
Michal Marek bed3c7
	$writer->endTag("request");
Michal Marek bed3c7
	$writer->end();
Michal Marek bed3c7
	$self->post("/request?cmd=create", $request);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub get_logfile {
Michal Marek bed3c7
	my ($self, $project, $package, $repository, $arch) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	$repository ||= "standard";
Michal Marek bed3c7
	return $self->get("/build/$project/$repository/$arch/$package/_log?nostream=1");
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek 7a4e6f
sub get_make_stderr {
Michal Marek 7a4e6f
	my ($self, $project, $package, $repository, $arch) = @_;
Michal Marek 7a4e6f
Michal Marek 7a4e6f
	$repository ||= "standard";
Michal Marek 7a4e6f
	return $self->get("/build/$project/$repository/$arch/$package/make-stderr.log");
Michal Marek 7a4e6f
}
Michal Marek 7a4e6f
Michal Marek 7a4e6f
Michal Marek bed3c7
sub get_kernel_commit {
Michal Marek bed3c7
	my ($self, $project, $package, $revision) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	my $content;
Michal Marek bed3c7
	for my $file (qw(source-timestamp build-source-timestamp)) {
Michal Marek bed3c7
		my $path = "/source/$project/$package/$file";
Michal Marek bed3c7
		$path .= "?rev=$revision" if $revision;
Michal Marek bed3c7
		eval {
Michal Marek bed3c7
			$content = $self->get($path);
Michal Marek bed3c7
		};
Michal Marek bed3c7
		last unless $@;
Michal Marek bed3c7
	}
Michal Marek bed3c7
	die "No timestamp file found in $project/$package\n" unless $content;
Michal Marek bed3c7
	if ($content !~ /^GIT Revision: ([0-9a-f]+)$/m) {
Michal Marek bed3c7
		die "Malformet timestamp file in $project/$package\n";
Michal Marek bed3c7
	}
Michal Marek bed3c7
	return $1;
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub get_results {
Michal Marek bed3c7
	my ($self, $project, $repository, $arch) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	my @params;
Michal Marek bed3c7
	push(@params, "repository=$repository") if $repository;
Michal Marek bed3c7
	push(@params, "arch=$arch") if $arch;
Michal Marek bed3c7
	my $xml = $self->get("/build/$project/_result?" . join("&", @params));
Michal Marek bed3c7
	return SUSE::MyBS::Buildresults->new($xml);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub load_results {
Michal Marek bed3c7
	my ($self, $file) = @_;
Michal Marek bed3c7
	my $xml = "";
Michal Marek bed3c7
Michal Marek bed3c7
	local $/ = undef;
Michal Marek bed3c7
	if (open(my $fh, '<', $file)) {
Michal Marek bed3c7
		$xml = <$fh>;
Michal Marek bed3c7
	};
Michal Marek bed3c7
	return SUSE::MyBS::Buildresults->new($xml);
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub list_projects {
Michal Marek bed3c7
	my $self = shift;
Michal Marek bed3c7
Michal Marek bed3c7
	return keys(%{$self->readdir("/source")});
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
sub delete_project {
Michal Marek bed3c7
	my ($self, $project) = @_;
Michal Marek bed3c7
Michal Marek bed3c7
	return $self->delete("/source/$project?force=1");
Michal Marek bed3c7
}
Michal Marek bed3c7
Michal Marek bed3c7
1;